How Does MVC 4 List Model Binding Work

How does MVC 4 List Model Binding work?

There is a specific wire format for use with collections. This is discussed on Scott Hanselman's blog here:

http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx

Another blog entry from Phil Haack talks about this here:

http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

Finally, a blog entry that does exactly what you want here:

http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/

Model Binding to a List MVC 4

This is how I do it if I need a form displayed for each item, and inputs for various properties. Really depends on what I'm trying to do though.

ViewModel looks like this:

public class MyViewModel
{
public List<Person> Persons{get;set;}
}

View(with BeginForm of course):

@model MyViewModel

@for( int i = 0; i < Model.Persons.Count(); ++i)
{
@Html.HiddenFor(m => m.Persons[i].PersonId)
@Html.EditorFor(m => m.Persons[i].FirstName)
@Html.EditorFor(m => m.Persons[i].LastName)
}

Action:

[HttpPost]public ViewResult(MyViewModel vm)
{
...

Note that on post back only properties which had inputs available will have values. I.e., if Person had a .SSN property, it would not be available in the post action because it wasn't a field in the form.

Note that the way MVC's model binding works, it will only look for consecutive ID's. So doing something like this where you conditionally hide an item will cause it to not bind any data after the 5th item, because once it encounters a gap in the IDs, it will stop binding. Even if there were 10 people, you would only get the first 4 on the postback:

@for( int i = 0; i < Model.Persons.Count(); ++i)
{
if(i != 4)//conditionally hide 5th item,
{ //but BUG occurs on postback, all items after 5th will not be bound to the the list
@Html.HiddenFor(m => m.Persons[i].PersonId)
@Html.EditorFor(m => m.Persons[i].FirstName)
@Html.EditorFor(m => m.Persons[i].LastName)
}
}

ASP.NET MVC 4 Model binding issue

Your Fiddler request shows what is emitted from the browser. It isn't sending your ExcludeClientsWithoutAddress property.

Since this property is not marked nullable bool? it is being assigned a default value in binding.

You have these inputs as ng_model which suggests your Angular code is not sending this field.

MVC4 - Partial View List Model binding during Submit to Main view

Interesting Question :)

The issue was that Model binding to list should have unique names. So the Generated HTML Should look like below:

<input id="Quotations_0__PRDocumentId" name="Quotations[0].PRDocumentId" type="hidden" value="0">

<input id="Quotations_1__PRDocumentId" name="Quotations[1].PRDocumentId" type="hidden" value="0">

The recommended solution is to use Editor Templates, Check this and this.

But I am giving alternate solution below using for loop to create unique names with index, taken from this post which faced same issue.

In Main View:

Pass The Main Model Instead

@Html.Partial("_PRDocs", Model)

Partial View:

@model JKLLPOApprovalApp.Models.PRDocument

<table class="table">
@if (Model.Quotations != null)
{
for (var i = 0; i < Model.Quotations.Count(); i++)
{
<tr>
<th>
@Html.DisplayNameFor(model => Model.Quotations[i].FileName)
</th>

<th></th>
</tr>

<tr>
@Html.HiddenFor(modelItem => Model.Quotations[i].PRDocumentId)
<td>
@Html.DisplayFor(modelItem => Model.Quotations[i].FileName)
</td>
<td>
@Html.ActionLink("Delete", "Delete", new { id = Model.Quotations[i].Id })
</td>
</tr>
}
}

</table>

Hope helps.

Asp mvc 4 model binding with post method

Your code seems fine. If the controller action you are posting to takes a BienModel as action parameter then binding should work fine:

[HttpPost]
public ActionResult SomeAction(BienModel model)
{
...
}

You might also take a look at the following article about the standard convention in ASP.NET MVC for binding to a list.

Also currently you only have an input field for the valor property. The nombre property doesn't have a corresponding input field so you will never get its value back. If you want that to happen you could use a hidden field:

@for (int i = 0; i < Model.Atributos.Count;i++ )
{
@Html.LabelFor(x => x.Atributos[i].valor, Model.Atributos[i].nombre)
@Html.HiddenFor(m => m.Atributos[i].nombre)
@Html.TextBoxFor(m => m.Atributos[i].valor)
@Html.ValidationMessageFor(m => m.Atributos[i].valor)
}

ASP.NET MVC 4.0 model binder not working with collection

You have defined your model with fields, but you have to use properties. You only have to change your model for this:

public class Company
{
public string Name { get; set; }
public List<CompanyActivity> Activities { get; set; }
}

public class CompanyActivity
{
public string Code { get; set; }
public string Description { get; set; }
}

Differences between fields and properties: Difference between Property and Field in C# 3.0+ and ASP.net MVC - Model binding excludes class fields?

ASP.Net MVC4 bind a create view to a model that contains List

I recently found myself needing to accomplish the same task and, like you, not wanting to add a bunch of javascript. I'm using MVC4 and, as best I can tell, there doesn't appear to be an out-of-the-box way to bind enumerable properties of a model to a view. :(

However, as you demonstrated in your question, it is possible to retrieve enumerable properties from the model in a view. The trick is just getting the updates back to the controller. Going off of your example models, your view could look like this (you don't need to make a partial):

@model MVCComplexObjects.Models.ComplexObject

<p>
@Html.ActionLink("Create New", "Create")
</p>

@using (Html.BeginForm("SaveNew", "Home", FormMethod.Post))
{
<table>

<tr>
<th>
@Html.DisplayNameFor(model => model.contents[0].name)
</th>
<th>
@Html.DisplayNameFor(model => model.contents[0].data)
</th>
<th></th>
</tr>

@for (int i = 0; i < Model.contents.Count; i++)
{
<tr>
<td>
@Html.TextBox("updatedContents["+i+"].name", Model.contents[i].name)
</td>
<td>
@Html.TextBox("updatedContents["+i+"].data", Model.contents[i].data)
</td>
<td>
@* Got rid of the edit and detail links here because this form can now act as both *@
@Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
</tr>
}

</table>

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

And your controller action would look like this:

[HttpPost]
public ActionResult SaveNew(ICollection<ContainedObject> updatedContents)
{
foreach (var co in updatedContents)
{
//Update the contained object...
}

return RedirectToAction("Index");
}

Basically, we are defining a new collection object in the view for MVC to pass to your action method upon form submission. The new object ("updatedContents" in this example) is basically the same as the list property ("contents", in this example) that was defined and populated in the ComplexObject model.

This is a bit more work, but does accomplish the goal of not needing any javascript for the post back. Everything can be done with standard MVC.



Related Topics



Leave a reply



Submit