'Form within a foreach loop not posting the model back to the controller
I've searched around and can't find an answer to my problem.
I've got a View that takes accepts Foo
in like so:
@model IEnumerable<MyApplication.Models.Foo>
Inside of the view I've got a table. I populate the the table just doing a simple foreach
loop through my Foo
model. I have added a column where a user can add details to each row in a text area and save.
So the view looks like this:
@foreach (var item in Model)
{
<tr>
<th>
@using (Html.BeginForm("AddDetails", "MyController", FormMethod.Post))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(u => item.Id)
@Html.TextAreaFor(u => item.Details, new { @class = "form-control" })
<input type="submit" value="Save" class="btn btn-sm btn-default" />
}
</th>
</tr>
}
My controller is setup like this:
[HttpPost]
public ActionResult AddDetails(Foo foo)
{
}
When I set a breakpoint on that controller foo
always has null
values for the Details
property and the Id
is always 0
. When it's passed into the view it's got all of the properties set to right, so I'm not sure why it is not posting back properly?
I've tried this:
@using (Html.BeginForm("AddDetails", "MyController", FormMethod.Post, new { foo = Model }))
I have also tried removing the foreach
loop and changing it to pass in just a single Model to the View and for the View to accept just the single model instead of an IEnumerable
. When I do this it posts back just fine.
And that does not work either. I'm wondering if it has something to do with the fact that my form is within a foreach
loop and perhaps I need to do a little more work like add a data-id
attribute to the element and just capture the click event on the button and do an AJAX call to the controller and pass the right values along that way?
EDIT Sorry I completely forgot the view model. Here is what that looks like:
public class Foo
{
public int Id { get; set; }
public string Details { get; set; }
}
Solution 1:[1]
Generally speaking it's bad form to post to a different model than what your form view is composed from. It makes a lot of things difficult, not the least of which is recovering from validation errors.
What you're doing here is looping through a list of Foo
and creating multiple forms that will each submit only a single Foo
to an action that takes only a single Foo
. While the post itself is fine, using the *For
helpers on a model that is not the same as what you're posting to will likely not generate the proper input names necessary for the modelbinder to bind the posted values onto that other model. At the very least, if you need to return to this view because of a validation error, you will not be able to keep the user's posted data, forcing them to repeat their efforts entirely.
Ideally, you should either post the entire list of Foo
and have just one form that wraps the iteration (in which case, you would need to use for
rather than foreach
). Or you should simply list the Foo
s with a link to edit a particular Foo
, where you would have a form for just one Foo
.
Solution 2:[2]
try to do this.possible problems
form should outside the table single form for whole table
wrap your column editor inside tbody not thead
@using (Html.BeginForm("AddDetails", "MyController", FormMethod.Post))
{
//thead and tbody
}
Solution 3:[3]
Since no answer was reported for testing the for loop instead of foreach
.
I was stuck all day yesterday using foreach loop and always got 0 items in the post method. I changed to for loop and was able to receive the updated items in the post method.
For reference I am using asp.net core 3.2
This is the foreach
loop:
@{
foreach(var role in @Model.RolesList)
{
<div class="btn-check m-1">
<input type="hidden" asp-for="@role.RoleId" />
<input type="hidden" asp-for="@role.RoleName" />
<input asp-for="@role.IsSelected" class="form-check-input" />
<label class="form-check-label" asp-for="@role.IsSelected">@role.RoleName</label>
</div>
}
}
This is the for
loop:
@for(int i = 0;i< Model.`enter code here`RolesList.Count();i++)
{
<div class="btn-check m-1">
<input type="hidden" asp-for="RolesList[i].RoleId" />
<input type="hidden" asp-for="RolesList[i].RoleName" />
<input asp-for="RolesList[i].IsSelected" class="form-check-input" />
<label class="form-check-label" asp-for="RolesList[i].IsSelected">@Model.RolesList[i].RoleName</label>
</div>
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Chris Pratt |
Solution 2 | |
Solution 3 | maxshuty |