Controller doesn't receive all properties back from the form

Viewed 74

I have a class QuestionnaireDetailsViewModel who contains some properties and also a list.

The list could contains from 0 to many items. I send the class QuestionnaireDetailsViewModel to my View in order to display it in a form. For the list part, I use a foreach to render it and it works just fine.

The problem is, all those properties are inputs and when I submit the form, my List is always empty when it's reaching the Controller.

I also noticed that in the payload of the call, it only send the first item in the list to the controller, but I believe it's because each item have the same name.

I don't understand how could I make match List properties name with each input of my form since they need to have a unique name. Is it possible to achieve this or should I save each list item separately with a JS loop?

Could you please enlighten me?

QuestionnaireDetailsViewModel class

public class QuestionnaireDetailsViewModel
{
    public string Id { get; set; }
    public string Name { get; set; }
    public List<VenueViewModel> VenueItems { get; set; }
}

VenueViewModel class

public class VenueViewModel
{
    public string Id { get; set; }
    public string Id_Que { get; set; }
    public string VenueName { get; set; }
    public string Checkbox_Other { get; set; }
    public string IsEventHaveSustainableApproach { get; set; }
}

View

<form method="post" enctype="multipart/form-data" class="form-content-column" id="formFull">
    <div class="input-box">
        <label class="numbered" for="Name">Name<span class="span-mandatory">*</span></label>
        <input asp-for="Name" class="input-mandatory" type="text" id="Name" required />
        <span asp-validation-for="Name"></span>
        <input asp-for="Id" type="hidden" id="Id" required />
    </div>
    <div class="accordion" id="accordionExample">
        @foreach (var obj in Model.VenueItems)
        {
            <div class="accordion-item">
                <h2 class="accordion-header" id="headingOne@obj.Id">
                    <button class="accordion-button accordion-button-custom" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne_@obj.Id" aria-expanded="true" aria-controls="collapseOne_@obj.Id" style="margin-top: 0px">
                        @obj.VenueName
                    </button>
                </h2>
                <div id="collapseOne_@obj.Id" class="accordion-collapse collapse show" aria-labelledby="headingOne_@obj.Id" data-bs-parent="#accordionExample">
                    <div class="accordion-body">
                        <div class="input-box no-border">
                            <label class="numbered">Did the venue have a sustainable approach?<span class="span-mandatory">*</span></label>
                            <div class="flex flex-column">
                                <div class="single-radio">
                                    <input asp-for="@obj.IsEventHaveSustainableApproach" class="input-mandatory radio_Tab1_IsEventHaveSustainableApproach" type="radio" value="1" id="Tab1_IsEventHaveSustainableApproach_Yes_@obj.Id" />
                                    <label for="Tab1_IsEventHaveSustainableApproach_Yes_@obj.Id">Yes</label>
                                </div>
                                <div class="single-radio">
                                    <input asp-for="@obj.IsEventHaveSustainableApproach" class="input-mandatory radio_Tab1_IsEventHaveSustainableApproach" type="radio" value="0" id="Tab1_IsEventHaveSustainableApproach_No_@obj.Id" />
                                    <label for="Tab1_IsEventHaveSustainableApproach_No_@obj.Id">No</label>
                                </div>
                            </div>
                        </div>
                        <div class="input-box">
                            <label for="Tab1_Checkbox_EventVenue_Other">Other? Please explain</label>
                            <input asp-for="@obj.Checkbox_Other" type="text" id="Tab1_Checkbox_EventVenue_Other" />
                        </div>
                    </div>
                </div>
            </div>
        }
    </div>
</form>

Js method to send the form

async function saveForm() {

    let formData = new FormData(document.forms[0]);
    
    await fetch('/Reports/Save', {
        method: 'post',
        body: formData
    })
        .then((response) => {
            toastr.success('Saved successfully');
            preventLeaving = false;
            return true;
        })
        .catch((error) => {
            toastr.error('An error occured');
            return false;
        });
}

Controller who receive the form back with the property Name filled but list items are empty

public async Task<IActionResult> SaveAsync(QuestionnaireDetailsViewModel questionnaireViewModel)
{
}
1 Answers

I also noticed that in the payload of the call, it only send the first item in the list to the controller, but I believe it's because each item have the same name

Yes, it is caused by the same name. For your nested list model, it binds the property by name="[index].PropertyName" or name="VenueItems[index].PropertyName".

Change your frontend code like below:

@model QuestionnaireDetailsViewModel
<form method="post" enctype="multipart/form-data" class="form-content-column" id="formFull">
    <div class="input-box">
        <label class="numbered" for="Name">Name<span class="span-mandatory">*</span></label>
        <input asp-for="Name" class="input-mandatory" type="text" id="Name" required />
        <span asp-validation-for="Name"></span>
        <input asp-for="Id" type="hidden" id="Id" required />
    </div>
    <div class="accordion" id="accordionExample">
        @{
            int i = 0;         //add index here........
        }
        @foreach (var obj in Model.VenueItems)
        {
            <div class="accordion-item">
                <h2 class="accordion-header" id="headingOne@obj.Id">
                    <button class="accordion-button accordion-button-custom" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne_@obj.Id" aria-expanded="true" aria-controls="collapseOne_@obj.Id" style="margin-top: 0px">
                        @obj.VenueName
                    </button>
                </h2>
                <div id="collapseOne_@obj.Id" class="accordion-collapse collapse show" aria-labelledby="headingOne_@obj.Id" data-bs-parent="#accordionExample">
                    <div class="accordion-body">
                        <div class="input-box no-border">
                            <label class="numbered">Did the venue have a sustainable approach?<span class="span-mandatory">*</span></label>
                            <div class="flex flex-column">
                                <div class="single-radio">
                                                       @*add name attribute*@
                                    <input name="VenueItems[@i].IsEventHaveSustainableApproach" asp-for="@obj.IsEventHaveSustainableApproach" class="input-mandatory radio_Tab1_IsEventHaveSustainableApproach" type="radio" value="1" id="Tab1_IsEventHaveSustainableApproach_Yes_@obj.Id" />
                                    <label for="Tab1_IsEventHaveSustainableApproach_Yes_@obj.Id">Yes</label>
                                </div>
                                <div class="single-radio">
                                    <input name="VenueItems[@i].IsEventHaveSustainableApproach" asp-for="@obj.IsEventHaveSustainableApproach" class="input-mandatory radio_Tab1_IsEventHaveSustainableApproach" type="radio" value="0" id="Tab1_IsEventHaveSustainableApproach_No_@obj.Id" />
                                    <label for="Tab1_IsEventHaveSustainableApproach_No_@obj.Id">No</label>
                                </div>
                            </div>
                        </div>
                        <div class="input-box">
                            <label for="Tab1_Checkbox_EventVenue_Other">Other? Please explain</label>
                                                                 @*add name attribute*@
                            <input asp-for="@obj.Checkbox_Other" name="VenueItems[@i].Checkbox_Other" type="text" id="Tab1_Checkbox_EventVenue_Other" />
                        </div>
                    </div>
                </div>
            </div>
            i++;          //add this.........
        }
    </div>
</form>

<button type="button" onclick="saveForm()">Post</button>
@section Scripts
{
    <script>
        async function saveForm() {
            let formData = new FormData(document.forms[0]);   
            await fetch('/Reports/Save', {
                method: 'post',
                body: formData
            })
                .then((response) => {
                    toastr.success('Saved successfully');
                    preventLeaving = false;
                    return true;
                })
                .catch((error) => {
                    toastr.error('An error occured');
                    return false;
                });
        }
    </script>
}

Result:

enter image description here

Related