For each loop with controls and submitting form (Html.BeginForm) in MVC 4
Asked Answered
M

2

9

In my view model, I have a list of objects. I iterate these objects, and create controls for each of them. In the situation below, I want to show people a textbox and a button for each object. When the user clicks the button, a post is made, and I can save the data in my controller.

In the UI, a user can change the form they want, and click save.

My problem is the model is null when it's posted to the controller..

My Razor code:

       using (Html.BeginForm())
        {
            foreach (var contributor in Model.Contributor)
            {

        @Html.HiddenFor(model => contributor.Id)

        <div class="formrow">
            @Html.ValidationSummary(true)
        </div>

        <h2>@Html.TextRaw("AuthorInfo", "Author")</h2>
        <div class="formrow">

            @Html.EditorFor(model => contributor.FirstName)
            <div class="formvalidation">
                @Html.ValidationMessageFor(model => contributor.FirstName)
            </div>
        </div>


        <div class="formrow right">
            <input type="hidden" name="formsubmitted" value="true" />
            <input type="submit"  class="button" value="@Html.Text("ButtonText", "Save")" />
        </div>
            }
        }

My view model code

public class ProfileModel
{
    public string Message { get; set; }
    public List<PublisherModel> Publisher { get; set; }
    public List<ContributorModel> Contributor { get; set; }

    public ContributorModel NewContributor { get; set; }
}

My controller code

[HttpPost]
public ActionResult Mine(ProfileModel model, string newuser)
{
    //
}

How to fix it?

I guess I have to expand my view model with a way to store the changes in some way. But I really can't see how.

Right now all the properties in the ProfileModel is null when it reaches the controller.

Any ideas?

Minister answered 26/3, 2013 at 11:45 Comment(4)
Somethings doesn't look right there, The model in the VIEW is List of ContributorModel, yet the model being posted (HttpPost) is ProfileModel? Its not the same model type as your editing/viewing!Greenness
Updated the view model code, so everything is shown :-)Minister
Is it still null if you post string Message aswell?Nippur
can you post your html rendered for @Html.EditorFor(model => contributor.FirstName)Legofmutton
N
21

Basically the problem is that default model binder is unable to bind collection items correctly in foreach loop. In other words, it names the element incorrectly and that's why the collection displays as null in parameters.

I'm sure there are all kinds of different workarounds, helpers and stuff but I'm not familiar with those, so I just use for loop instead of foreach, this way the elements are named correctly.

Try this:

    @for (int i = 0; i < Model.Contributor.Count(); i++)
    {

        @Html.HiddenFor(model => Model.Contributor[i].Id)

        <div class="formrow">
            @Html.ValidationSummary(true)
        </div>

        <h2>@Html.TextRaw("AuthorInfo", "Author")</h2>
        <div class="formrow">

            @Html.EditorFor(model => Model.Contributor[i].FirstName)
            <div class="formvalidation">
                @Html.ValidationMessageFor(model => Model.Contributor[i].FirstName)
            </div>
        </div>


        <div class="formrow right">
            <input type="hidden" name="formsubmitted" value="true" />
            <input type="submit"  class="button" value="@Html.Text("ButtonText", "Save")" />
        </div>
    }

I suggest you to use a debugging tool to see if elements have correct name attribute, in your case they should look like Contributor[0].Id, Contributor[0].FirstName etc.

Nippur answered 26/3, 2013 at 12:1 Comment(4)
I did something almost exactly like this recently; the model binder can't bind to a list if it can't see an index for each object, hence the need for Contributor[i]. If you don't like the for loop, there's also foreach ( var thing in someCollection.Select((x,i) => new { Value = x, Index=i }) ) which will give you thing.Index as the index in the loop (in place of i in a for loop) and thing.Value as the object.Gizmo
I wouldn't have thought of that solution.. Thanks a lot! Really helps :-)Minister
the funny thing is that visual studio suggests me to convert the for-loop to a foreach-loop and then the code stops working since the data isn't bound correctly once it's postedChuddar
I know this an old thread. I am working on a newer project and ran into this issue and this solution worked great.Alongside
G
5

You can use PartialView for Contributor object. PartialView:

@model Contributor
using (Html.BeginForm("ContributorUpdate", "YourController"))
{
  @Html.HiddenFor(model => Model.Id)
  <div class="formrow">
    @Html.ValidationSummary(true)
  </div>

  <h2>@Html.TextRaw("AuthorInfo", "Author")</h2>
  <div class="formrow">

   @Html.EditorFor(model => Model.FirstName)
   <div class="formvalidation">
        @Html.ValidationMessageFor(model => Model.FirstName)
   </div>
</div>
<div class="formrow right">
   <input type="hidden" name="formsubmitted" value="true" />
   <input type="submit"  class="button" value="@Html.Text("ButtonText", "Save")" />
</div>
}

View will be:

@foreach (var contributor in Model.Contributor)
{
   @{Html.RenderPartial("Conributor", contributor);}
}

And controller code:

[HttpPost]
public ActionResult Mine(Conributor conributor, string newuser)
{
    //
}
Genagenappe answered 26/3, 2013 at 12:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.