'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 Foos 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