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" id="formFull">
<div >
<label for="Name">Name<span >*</span></label>
<input asp-for="Name" type="text" id="Name" required />
<span asp-validation-for="Name"></span>
<input asp-for="Id" type="hidden" id="Id" required />
</div>
<div id="accordionExample">
@foreach (var obj in Model.VenueItems)
{
<div >
<h2 id="[email protected]">
<button type="button" data-bs-toggle="collapse" data-bs-target="#[email protected]" aria-expanded="true" aria-controls="[email protected]" style="margin-top: 0px">
@obj.VenueName
</button>
</h2>
<div id="[email protected]" aria-labelledby="[email protected]" data-bs-parent="#accordionExample">
<div >
<div >
<label >Did the venue have a sustainable approach?<span >*</span></label>
<div >
<div >
<input asp-for="@obj.IsEventHaveSustainableApproach" type="radio" value="1" id="[email protected]" />
<label for="[email protected]">Yes</label>
</div>
<div >
<input asp-for="@obj.IsEventHaveSustainableApproach" type="radio" value="0" id="[email protected]" />
<label for="[email protected]">No</label>
</div>
</div>
</div>
<div >
<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)
{
}
CodePudding user response:
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" id="formFull">
<div >
<label for="Name">Name<span >*</span></label>
<input asp-for="Name" type="text" id="Name" required />
<span asp-validation-for="Name"></span>
<input asp-for="Id" type="hidden" id="Id" required />
</div>
<div id="accordionExample">
@{
int i = 0; //add index here........
}
@foreach (var obj in Model.VenueItems)
{
<div >
<h2 id="[email protected]">
<button type="button" data-bs-toggle="collapse" data-bs-target="#[email protected]" aria-expanded="true" aria-controls="[email protected]" style="margin-top: 0px">
@obj.VenueName
</button>
</h2>
<div id="[email protected]" aria-labelledby="[email protected]" data-bs-parent="#accordionExample">
<div >
<div >
<label >Did the venue have a sustainable approach?<span >*</span></label>
<div >
<div >
@*add name attribute*@
<input name="VenueItems[@i].IsEventHaveSustainableApproach" asp-for="@obj.IsEventHaveSustainableApproach" type="radio" value="1" id="[email protected]" />
<label for="[email protected]">Yes</label>
</div>
<div >
<input name="VenueItems[@i].IsEventHaveSustainableApproach" asp-for="@obj.IsEventHaveSustainableApproach" type="radio" value="0" id="[email protected]" />
<label for="[email protected]">No</label>
</div>
</div>
</div>
<div >
<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: