Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC binding to model with list property ignores other properties

I have a basic ViewModel with a property that is a List of complex types. When binding, I seem to be stuck with getting either the list of values, OR the other model properties depending on the posted values (i.e. the view arrangement).

The view model:

public class MyViewModel {     public int Id { get; set; }     public string Property1 { get; set; }     public string Property2 { get; set; }      public List<MyDataItem> Data { get; set; } }  public class MyDataItem {     public int Id { get; set; }     public int ParentId { get; set; }     public string Name { get; set; }     public string Value { get; set; } } 

The controller actions:

    public ActionResult MyForm()     {         MyViewModel model = new MyViewModel();          model.Id = 1;         model.Data = new List<MyDataItem>()          {              new MyDataItem{ Id = 1, ParentId = 1, Name = "MyListItem1", Value = "SomeValue"}         };           return View(model);     }      [HttpPost]     public ActionResult MyForm(MyViewModel model)     {         //...          return View(model);     } 

Here is the basic view (without the list mark-up)

@using (Html.BeginForm()) {     @Html.ValidationSummary(true)     <fieldset>         <legend>My View Model</legend>          @Html.HiddenFor(model => model.Id)          <div class="editor-label">             @Html.LabelFor(model => model.Property1)         </div>         <div class="editor-field">             @Html.EditorFor(model => model.Property1)             @Html.ValidationMessageFor(model => model.Property1)         </div>          <div class="editor-label">             @Html.LabelFor(model => model.Property2)         </div>         <div class="editor-field">             @Html.EditorFor(model => model.Property2)             @Html.ValidationMessageFor(model => model.Property2)         </div>          <p>             <input type="submit" value="Save" />         </p>     </fieldset> } 

When posted back to the controller, I get the 2 property values and a null value for the 'Data' property as expected.

Without List Mark-up

If I add the mark-up for the List as follows (based on the information in this Scott Hanselman post and Phil Haack's post):

<div class="editor-field"> @for (int i = 0; i < Model.Data.Count(); i++) {     MyDataItem data = Model.Data[i];     @Html.Hidden("model.Data[" + i + "].Id", data.Id)     @Html.Hidden("model.Data[" + i + "].ParentId", data.ParentId)     @Html.Hidden("model.Data[" + i + "].Name", data.Name)     @Html.TextBox("model.Data[" + i + "].Value", data.Value) } </div> 

The 'Data' property of the model is successfully bound but the other properties are null.

With List Mark-up

The form values posted are as follows:

Id=1&Property1=test1&Property2=test2&model.Data%5B0%5D.Id=1&model.Data%5B0%5D.ParentId=1&model.Data%5B0%5D.Name=MyListItem1&model.Data%5B0%5D.Value=SomeValue 

Is there a way to get both sets of properties populated or am I just missing something obvious?

EDIT:

For those of you who are curious. Based on the answer from MartinHN, the original generated mark-up was:

<div class="editor-field">     <input id="model_Data_0__Id" name="model.Data[0].Id" type="hidden" value="1" />     <input id="model_Data_0__ParentId" name="model.Data[0].ParentId" type="hidden" value="1" />     <input id="model_Data_0__Name" name="model.Data[0].Name" type="hidden" value="MyListItem1" />     <input id="model_Data_0__Value" name="model.Data[0].Value" type="text" value="SomeValue" />         </div> 

The new generated mark-up is:

<div class="editor-field">     <input id="Data_0__Id" data-val="true" name="Data[0].Id" type="hidden" value="1" data-val-number="The field Id must be a number." data-val-required="The Id field is required." />     <input id="Data_0__ParentId" name="Data[0].ParentId" type="hidden" value="1"  data-val="true" data-val-number="The field ParentId must be a number." data-val-required="The ParentId field is required." />     <input id="Data_0__Name" name="Data[0].Name" type="hidden" value="MyListItem1" />     <input id="Data_0__Value" name="Data[0].Value" type="text" value="SomeValue" />         </div> 

Which results in the following posted values:

Id=1&Property1=test1&Property2=test2&Data%5B0%5D.Id=1&Data%5B0%5D.ParentId=1&Data%5B0%5D.Name=MyListItem1&Data%5B0%5D.Value=SomeValue 

Notice there's no 'model.' in the name and posted values...

like image 592
robmzd Avatar asked Jul 05 '11 16:07

robmzd


People also ask

How does MVC model binding work?

Model binding is a mechanism ASP.NET MVC uses to create parameter objects defined in controller action methods. The parameters can be of any type, from simple to complex ones. It simplifies working with data sent by the browser because data is automatically assigned to the specified model.

Does MVC use data binding?

MVC doesn't use data bindings like old web api. You have to use model bindings in a MVC or MVVM approach.

Which variable does model binding depend on?

Now the magic of Model Binding depends on the id of HTML variables that are supplying the values. For our Employee Model, the id of the HTML input fields should be the same as the Property names of the Employee Model and you can see that Visual Studio is using the same property names of the model while creating a view.


2 Answers

Try to change the code for the Data collection to this, and let MVC take care of the naming:

<div class="editor-field"> @for (int i = 0; i < Model.Data.Count(); i++) {     @Html.HiddenFor(m => m.Data[i].Id)     @Html.HiddenFor(m => m.Data[i].ParentId)     @Html.HiddenFor(m => m.Data[i].Name)     @Html.TextBoxFor(m => m.Data[i].Value) } </div> 
like image 104
MartinHN Avatar answered Nov 09 '22 12:11

MartinHN


Alternatively you could have created an EditorTemplate for your nested ViewModel as follows.

@model MyDataItem @Html.HiddenFor(model => model.Id) @Html.HiddenFor(model => model.ParentId) @Html.HiddenFor(model => model.Name) @Html.TextBoxFor(model => model.Value) 

Create a folder named 'EditorTemplates' in your 'Shared' folder and save the above as 'MyDataItem.cshtml'.

Then in your View, just call the following instead of the foreach loop:

@Html.EditorFor(model => model.Data) 

Feels a bit less hackier IMO :)

like image 27
Ε Г И І И О Avatar answered Nov 09 '22 10:11

Ε Г И І И О