niao
niao

Reputation: 5070

MVC4 - binding nested collection

I have two classes:

public class Exercise
{
  public Guid Id {get;set;}
  public string Name {get;set;}
  public List<ExerciseItem> Items {get;set;}
}

public class ExerciseItem
{
  public Guid Id {get;set;}
  public string Name {get;set;}
  public string Content {get;set;}
}

I also have my view for creating an Exercise object. There is a button on that view named "Add exercise item" where I dynamically invoke ajax method for returning a partial view for ExerciseItem objects. The view is returned correctly. This view is as follows:

@model Elang.Models.ExerciseItem
<div>
   <input type="hidden" name="Items.Index" value="@Model.Id" />
   <input type="hidden" id="Items@(Model.Id)__Id" name="Items[@Model.Id].Id" value="@Model.Id" />
   <input type="text" id="Items@(Model.Id)__Content" name="Items[@Model.Id].Content" class="inputText"/>
</div>

The problem is that when I submit the form and my "Create" method is invoked:

[HttpPost]
public ActionResult Create(Exercise exercise)
{
    //add exercise to db
    //HOWEVER!!
    //exercise.Items is empty
}

my Items are null. What am I doing wrong? Can someone give me some advices what should I do to fix the problem?

Upvotes: 1

Views: 2088

Answers (1)

Erik Funkenbusch
Erik Funkenbusch

Reputation: 93424

I'm really not sure why so many people insist on NOT using the helper methods, and want to do all their form fields themselves. Helpers will make sure your format is correct, so long as you use them correctly.

However, your real problem here is an impedance mismatch between the Exercise model your ActionMethod requires, and the ExerciseItem model that your PartialView uses. You're using your Partial as an EditorTemplate, and this causes problems.

Instead, use an EditorTemplate, and use helper methods, and everything will work out correctly.

Create a folder called EditorTemplates in the View path (either the local folder, or in Shared views) and add a file called ExerciseItem.cshtml. In that file, use this code:

@model Elang.Models.ExerciseItem
<div>
    @Html.Hidden(m => m.Id)
    @Html.TextBoxFor(m => m.Content)
</div>

Then, in your parent view, just use the following and it will automatically iterate over all the collections and generate the correctly named fields:

@Html.EditorFor(m => m.Items)

If you still want to use Ajax to retrieve the items, then put the EditorFor inside a partial view, and have the partial view take the Exercise model.

Simply put, when rendering collections, USE Editor/Display Tempates. It will make your life so much easier, and prevent the headaches of getting your form field naming conventions in the correct format that the model binder expects.

I think at least 50% of the questions here on SO of the "My model is null!" variety would not happen if they were using EditorTemplates correctly

Upvotes: 6

Related Questions