Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ViewModel's list is null in action

I'm working on my first ASP.NET MVC 3 application and I've got a View that looks like this:

@model IceCream.ViewModels.Note.NotesViewModel
@using (Html.BeginForm())
{
    @Html.ValidationSummary(true)
    @Html.TextBoxFor(m => m.Name)

    foreach (var item in Model.Notes)
    {
        @Html.EditorFor(m => item);
    }

    <input type="submit" value="Submit"/>
}

And I have an EditorTemplate that looks like this:

@model IceCream.ViewModels.Note.NoteViewModel
<div>
    @Html.HiddenFor(m => m.NoteID)
    @Html.TextBoxFor(m => m.NoteText)
    @Html.CheckBoxFor(m => m.IsChecked)
</div>

NotesViewModel looks like so:

    public class NotesViewModel
    {
        public string Name { get; set; }
        public IEnumerable<NoteViewModel> Notes { get; set; }
    }

NoteViewModel looks like this:

public class NoteViewModel
{
    public int NoteID { get; set; }
    public System.DateTime Timestamp { get; set; }
    public string NoteText { get; set; }
    public bool IsChecked { get; set; }
}

The NotesViewModel is populated just fine when it is passed to the view. However when the submit button is clicked, the controller action handling the post has only the value for the Name property of the viewmodel. The Notes property - the list of notes that have been checked/unchecked by the user - is null. I've got a disconnect between the populating of those TextBoxFor and CheckBoxFor elements when the view is displayed and the ViewModel being sent back. Guidance on this?


SOLUTION Thanks go to Mystere Man for setting me straight on this. As I understand it, essentially by changing my loop to

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

changes the underlying HTML, which I understand provides for the proper model binding on the post. Looking at the resulting HTML, I see that I get the following generated for one of the Notes:

<div>
  <input id="Notes_0__NoteId" type="hidden" value="1" name="Notes[0].NoteId">
  <input id="Notes_0__NoteText" type="text" value="Texture of dessert was good." name="Notes[0].NoteText">
  <input id="Notes_0__IsChecked" type="checkbox" value="true" name="Notes[0].IsChecked>
</div>

Which is different than this HTML generated by my original code:

<div>
   <input id="item_NoteId" type="hidden" value="1" name="item.NoteId>
   <input id="item_NoteText" type="text" value="Texture of dessert was good." name="item.NoteText" >
   <input id="item_IsChecked" type="checkbox" value="true" name="item.IsChecked">
</div>

By looping through the Notes, the generated HTML essentially loses any references to the viewmodel's Notes property and while the HTML gets populated correctly, the setting of the checkbox values has no way to communicate their values back to the viewmodel, which I guess is the point of the model binding.

So I learned something, which is good.

like image 291
itsmatt Avatar asked Aug 23 '11 02:08

itsmatt


2 Answers

You're a smart guy, so look at your view. Then, consider how the HTML gets generated. Then, consider how on postback the Model Binder is supposed to know to re-populate Notes based on the generated HTML.

I think you'll find that your HTML doesn't have enough information in it for the Model Binder to figure it out.

Consider this:

@EditorFor(m => Model.Notes)

Rather than the for loop where you are basically hiding the context from the EditorFor function.

like image 78
Erik Funkenbusch Avatar answered Nov 19 '22 17:11

Erik Funkenbusch


And for those that just want the answer as a for loop:

@for (int x = 0; x < Model.Notes.Count(); x++) {
    @Html.HiddenFor(m => m.Notes[x].NoteId)
    @Html.EditorFor(m => m.Notes[x].NoteText)
    @Html.EditorFor(m => m.Notes[x].IsChecked)
}
like image 5
MartinC Avatar answered Nov 19 '22 15:11

MartinC