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
JavaScript Date to C# via Ajax
What Are Major Differences Between C# and Java
What's the Difference Between System.Type and System.Runtimetype in C#
What Is the Effect of Asenumerable() on a Linq Entity
How to Get the Range of Occupied Cells in Excel Sheet
Simultaneous Read-Write a File in C#
Dbentityvalidationexception - How to Easily Tell What Caused the Error
In C#, What Happens When You Call an Extension Method on a Null Object
Modelstate.Isvalid == False, Why
Pass C# ASP.NET Array to JavaScript Array
Why Aren't Variables Declared in "Try" in Scope in "Catch" or "Finally"
Where Does Error Cs0433 "Type 'X' Already Exists in Both A.Dll and B.Dll " Come From
Convert Variable to Type Only Known at Run-Time
How to Detect Keypress While Not Focused
Multiple String Comparison with C#