Model Binding To A List

Download the sample project to play with the code as you read this blog post.

Using the DefaultModelBinder in ASP.NET MVC, you can bind submitted form values to arguments of an action method. But what if that argument is a collection? Can you bind a posted form to an ICollection<T>?

Sure thing! It’s really easy if you’re posting a bunch of primitive types. For example, suppose you have the following action method.

public ActionResult UpdateInts(ICollection<int> ints) {

  return View(ints);
}

You can bind to that by simply submitting a bunch of form fields each having the same name. For example, here’s an example of a form that would bind to this, assuming you keep each value a proper integer.

<form method="post" action="/Home/UpdateInts">
    <input type="text" name="ints" value="1" />
    <input type="text" name="ints" value="4" />
    <input type="text" name="ints" value="2" />
    <input type="text" name="ints" value="8" />
    <input type="submit" />
</form>

If you were to take fiddler and look at what data actually gets posted when clicking the submit button, you’d see the following.

ints=1&ints=4&ints=2&ints=8

The default model binder sees all these name/value pairs with the same name and converts that to a collection with the key ints, which is then matched up with the ints parameter to your action method. Pretty simple!

Where it gets trickier is when you want to post a list of complex types. Suppose you have the following class and action method.

public class Book {
    public string Title { get; set; }
    public string Author { get; set; }
    public DateTime DatePublished { get; set; }
}

//Action method on HomeController
public ActionResult UpdateProducts(ICollection<Book> books) {
    return View(books);
}

You might think we could simply post the following to that action method:

Title=title+one&Author=author+one&DateTime=1/23/1975
&Title=author+two&Author=author+two&DateTime=6/6/2007…

Notice how we simply repeat each property of the book in the form post data? Unfortunately, that wouldn’t be a very robust approach. One reason is that we can’t distinguish from the fact that there may well be another Title input unrelated to our list of books which could throw off our binding.

Another reason is that the checkbox input does not submit a value if it isn’t checked. Most input fields, when left blank, will submit the field name with a blank value. With a checkbox, neither the name nor value is submitted if it’s unchecked! This again can throw off the ability of the model binder to match up submitted form values to the correct object in the list.

To bind complex objects, we need to provide an index for each item, rather than relying on the order of items. This ensures we can unambiguously match up the submitted properties with the correct object.

Here’s an example of a form that submits three books.

<form method="post" action="/Home/Create">

    <input type="text" name="[0].Title" value="Curious George" />
    <input type="text" name="[0].Author" value="H.A. Rey" />
    <input type="text" name="[0].DatePublished" value="2/23/1973" />
    
    <input type="text" name="[1].Title" value="Code Complete" />
    <input type="text" name="[1].Author" value="Steve McConnell" />
    <input type="text" name="[1].DatePublished" value="6/9/2004" />
    
    <input type="text" name="[2].Title" value="The Two Towers" />
    <input type="text" name="[2].Author" value="JRR Tolkien" />
    <input type="text" name="[2].DatePublished" value="6/1/2005" />
    
    <input type="submit" />
</form>

Note that the index must be an unbroken sequence of integers starting at 0 and increasing by 1 for each element.

The new expression based helpers in ASP.NET MVC 2 will produce the correct format within a for loop. Here’s an example of a view that outputs this format:

<%@ Page Inherits="ViewPage<IList<Book>>" %>

<% for (int i = 0; i < 3; i++) { %>

  <%: Html.TextBoxFor(m => m[i].Title) %>
  <%: Html.TextBoxFor(m => m[i].Author) %>
  <%: Html.TextBoxFor(m => m[i].DatePublished) %> 

<% } %>

It also works with our templated helpers. For example, we can take the part inside the for loop and put it in a Books.ascx editor template.

<%@ Control Inherits="ViewUserControl<Book>" %>

<%: Html.TextBoxFor(m => m.Title) %>
<%: Html.TextBoxFor(m => m.Author) %>
<%: Html.TextBoxFor(m => m.DatePublished) %> 

Just add a folder named EditorTemplates within the Views/Shared folder and add Books.ascx to this folder.

Now change the original view to look like:

<%@ Page Inherits="ViewPage<IList<Book>>" %>

<% for (int i = 0; i < 3; i++) { %>

  <%: Html.EditorFor(m => m[i]) %>

<% } %>

Non-Sequential Indices

Well that’s all great and all, but what happens when you can’t guarantee that the submitted values will maintain a sequential index? For example, suppose you want to allow deleting rows before submitting a list of books via JavaScript.

The good news is that by introducing an extra hidden input, you can allow for arbitrary indices. In the example below, we provide a hidden input with the .Index suffix for each item we need to bind to the list. The name of each of these hidden inputs are the same, so as described earlier, this will give the model binder a nice collection of indices to look for when binding to the list.

<form method="post" action="/Home/Create">

    <input type="hidden" name="products.Index" value="cold" />
    <input type="text" name="products[cold].Name" value="Beer" />
    <input type="text" name="products[cold].Price" value="7.32" />
    
    <input type="hidden" name="products.Index" value="123" />
    <input type="text" name="products[123].Name" value="Chips" />
    <input type="text" name="products[123].Price" value="2.23" />
    
    <input type="hidden" name="products.Index" value="caliente" />
    <input type="text" name="products[caliente].Name" value="Salsa" />
    <input type="text" name="products[caliente].Price" value="1.23" />
    
    <input type="submit" />
</form>

Unfortunately, we don’t have a helper for generating these hidden inputs. However, I’ve hacked together an extension method which can render this out for you.

When you’re creating a form to bind a list, add the following hidden input and it will add the appropriate hidden input to allow for a broken sequence of indices. Use at your own risk! I’ve only tested this in a couple of scenarios. I’ve included a sample project with multiple samples of binding to a list which includes the source code for this helper.

<%: Html.HiddenIndexerInputForModel() %>

This is something we may consider adding to a future version of ASP.NET MVC. In the meanwhile, give it a whirl and let us know how it works out for you.

Technorati Tags: ,

What others have said

Requesting Gravatar... Russell Garner Oct 24, 2008 12:08 AM
# re: Model Binding To A List
I recently wrote a content moderation system using MonoRail which used a similar binding strategy. I was wondering if/when ASP.NET MVC would support it. It has a similar syntax to your example, although it only deals with arrays rather than ILists (fair enough really - you're only going to have had a fixed number of things posted back).

The use case for us was this: moderators need to lock and work on a batch of 10 blog comments/forum posts at once. They accept/reject this content by selecting an action from a drop down, and can amend the content to make it suitable for publication. Once this is done, all 10 items - along with the actions taken - are posted back to the server. An appropriate ModerationAction DTO is used to pull this data back.

If this kind of functionality had not existed in MonoRail, we'd have had to write it (it was a non-negotiable use case), so I'm pretty pleased to see ASP.NET MVC is getting it. As to the lack of helpers here, I'm not sure that's huge. I got by just generating the [indexes] from the (NVelocity) view with a variable.
Requesting Gravatar... Gauthier Segay Oct 24, 2008 12:17 AM
# re: Model Binding To A List
Does the hidden index field is mandatory?

As said by Russel Garner, MonoRail implementation is similar but doesn't need that hint, it doesn't feel usefull to put hidden index fields in the view.

Also, will the default binder work on composite types containing arrays/lists? I've a similar use case in MonoRail where I bind array of DTO themselves containing array of enumeration values or integral values, it seems the MonoRail binder can work with any level of deepness, but I'm unsure with ASP.NET MVC's DefaultModelBinder (especially if the hidden index thing is mandatory).

I guess I've to try it out :)

Last thing, is there anything in the HTTP & HTML spec that ensure that form values would be sent in the same order as displayed in HTML? I'm just worried that there could be issues with proxy or exotic browser settings...
Requesting Gravatar... Andrew Oct 24, 2008 12:55 AM
# re: Model Binding To A List
Thanks so much for this, been waiting since beta was released to figure out how to do this!!
Requesting Gravatar... dmitry39 Oct 24, 2008 1:28 AM
# re: Model Binding To A List
great article, as allways, thx
Requesting Gravatar... Paul Oct 24, 2008 1:45 AM
# re: Model Binding To A List
Hi Phil,

Any chance of a sentence or two on why the hidden index is needed?

Regards,

Paul
Requesting Gravatar... Matt J Oct 24, 2008 3:14 AM
# re: Model Binding To A List
Hi Phil,

Great article, glad that someone finally documented this from M$ - but now that I'm using the DefaultModelBinder it's forced me to come away from using a few custom model binders I had that basically handled "Linq to SQL" objects, and I like the clean approach. Only problem is (this might be a tiny bit off-topic) that I don't think the DefaultModelBinder will handle Nullable properties on a Type that it's binding to - in my instance, it's a nullable foreign key reference (Guid) that always comes back as populated (default(Guid)) - leaving me with ForeignKeyReferenceAlreadyHasValueException's
Requesting Gravatar... Melvyn Harbour Oct 24, 2008 3:33 AM
# re: Model Binding To A List
Phil,

There's one potential problem with the suggested way of using the name attribute in that way surely. In the HTML 4.0 spec, the '[' and ']' characters are not part of the accepted list of characters for the name attribute. See the specification. If you're in XHTML, it shouldn't be a problem as the name attribute is specified as CDATA, but it is an important difference between the two. It means that we are now restricted to using XHTML, not necessarily a bad thing, but worth remembering since MVC does allow us fine grained control over the (X)HTML.
Requesting Gravatar... Jonathon Wilson Oct 24, 2008 5:11 AM
# re: Model Binding To A List
Thanks for posting this -- I've been searching for various solutions to this (List of complex objects) for a while. In one case, I ended up doing something very similar to what you described, indexes on the fields, but using a custom model binder to recompose the list. In a second case, I kept a backing javascript object model in the browser, and simply provided a single field with the JSON of my serialized list, and deserialized in a model binder. That was definitely less code, but has the dependency on javascript (which was fine in my particular case). I'll take a look at the way you've described.
Requesting Gravatar... Vladan Strigo Oct 24, 2008 5:23 AM
# re: Model Binding To A List
Phil,

Could you tell me for extensibility of binding in beta... I made my own FetchAndBindAttribute in P5, and now want to convert it to beta. Because of this function:

private static Predicate GetPropertyFilter(ParameterInfo parameterInfo) {
BindAttribute attr = (BindAttribute)Attribute.GetCustomAttribute(parameterInfo, typeof(BindAttribute));
return (attr != null) ? (Predicate)attr.IsPropertyAllowed : null;
}

My natural idea was that my attribute should inherit from BindAttribute. However... BindAttribute is sealed?!?!?!!?!?


Any ideas?

Thnx!

Vladan
Requesting Gravatar... Ben Hart Oct 24, 2008 5:46 AM
# re: Model Binding To A List
Hi Phil

Very interesting. We've rolled our own similar solution... Simlar to what's already been asked, how is the index as defined in the value reflected in the list once posted? If we posted the sample above with index 1 missing, would the parameter have a list of length 3, with a null at index 1?

I should play around and find out myself, just feeling lazy on Friday...

Thanks! Ben
Requesting Gravatar... haacked Oct 24, 2008 8:00 AM
# re: Model Binding To A List
@Melvyn I don't see anything in the HTML spec disallowing "[" characters in the name. You posted a link to something about the name of Frames, which has nothing to do with this. Did you have an updated link?
Requesting Gravatar... Melvyn Harbour Oct 24, 2008 8:35 AM
# re: Model Binding To A List
That's strange - it should be a link to section 6.2 of the HTML spec (SGML basic types). I've realised after a bit of more detailed reading that I made a slight mistake. The line in the spec says that:

"ID and NAME tokens must begin with a letter ([A-Za-z]) and may be followed by any number of letters, digits ([0-9]), hyphens ("-"), underscores ("_"), colons (":"), and periods (".")."

However if you look at the definition of the input element, it's name attribute is marked as CDATA, not NAME (confusingly!). So actually nothing to worry about and XHTML and HTML appear to be consistent after all!
Requesting Gravatar... levib Oct 24, 2008 12:06 PM
# re: Model Binding To A List
Phil asked me to take a look at the comments and to give some feedback. Hopefully this will answer some of the questions. :)

@Gauthier - At the moment, the hidden index field is mandatory for binding lists of complex types (e.g. types that aren't structs or strings). This is due to a current limitation of the IValueProvider interface. We're considering changing the IValueProvider to make it slightly more powerful, which will negate the need for the hidden field. Also, AFAIK the HTML forms spec doesn't mandate an order, but in practice all browsers post the values in the order in which they appear in the document.

@Matt - Can you provide a simple repro of the problem you're experiencing? We've gone to great lengths to make sure that nullable types work properly, but it's possible that our tests missed something.

@Vladan - What are you trying to do here? The [Bind] attribute is sealed because it's a simple metadata attribute; there should be no need to subclass it. Did you instead mean to subclass the abstract CustomModelBinderAttribute type?

@Ben - The index in the form is distinct from the index in the generated list. The index in the form is allowed to be any arbitrary string, but we'll always convert these to a zero-based list. It's the ordering that matters.

@Melvyn - I've been hitting my head against a wall trying to read the spec. Suffice it to say that DTDs aren't my native tongue. :) At any rate, note that at the moment we don't have helpers that generate the [] syntax (and in fact, we have an open bug regarding the fact that ViewData.Eval() has its own syntax for specifying indexes). Also, note that the MVC HTML helpers are actually XHTML helpers. Some of the helpers do not produce valid HTML 4.01, but they all [should] produce valid XHTML 1.0.
Requesting Gravatar... haacked Oct 24, 2008 12:33 PM
# re: Model Binding To A List
@Melvyn Thanks for following up! I appreciate that. :)
Requesting Gravatar... Vladan Strigo Oct 24, 2008 2:43 PM
# re: Model Binding To A List
Levib/Phil,

I am trying to roll out my own impl. which has an additional parameter called FetchBehavior.

Now, what I want is when someone defines a [FetchAndBind] attribute that it does everything [Bind] does and more (basically gets also a new FetchBehavior param and assigns a special kind of IBinder).

The problem is that in methods:


private static string GetFieldPrefix(ParameterInfo parameterInfo) {
BindAttribute attr = (BindAttribute)Attribute.GetCustomAttribute(parameterInfo, typeof(BindAttribute));
return ((attr != null) && (attr.Prefix != null)) ? attr.Prefix : parameterInfo.Name;
}
private static Predicate GetPropertyFilter(ParameterInfo parameterInfo) {
BindAttribute attr = (BindAttribute)Attribute.GetCustomAttribute(parameterInfo, typeof(BindAttribute));
return (attr != null) ? (Predicate)attr.IsPropertyAllowed : null;
}


your relying on an direct implementation of BindAttribute. Now, if I want to reuse the logic of IsPropertyAllowed and ModelName, my attribute must subclass BindAttribute. And that cannot be done.

Is that any clearer? Please let me know if I was not able to explain, I will create a code sample of what I want to achieve.


Thanks!


Vladan
Requesting Gravatar... Vladan Strigo Oct 24, 2008 2:46 PM
# re: Model Binding To A List
Also... the alternative (which I've implemented now) is that I send include/exclude properties lists and prefix to the constructor of customer binder, and in the binder itself execute the logic of those 2 functions manually.

As you can imagine... its a hack.


Thanks!

Vladan
Requesting Gravatar... Ricky Supit Oct 24, 2008 7:18 PM
# re: Model Binding To A List
@Vladan: What I did is to create a new ModelBindingContext and pass property filter delegate like the following:

BindAttribute bindAttr = new BindAttribute() { Exclude = this.Exclude, Include = this.Include };
propFilter = (propName) =>
bindAttr.IsPropertyAllowed(propName) && bindingContext.ShouldUpdateProperty(propName);

It helps if at least the static version of IsPropertyAllowed be made public.

Here is my implementation of CslaBindAttribute: www.codeplex.com/.../ProjectReleases.aspx

Ricky
Requesting Gravatar... Joshua Oct 24, 2008 10:52 PM
# re: Model Binding To A List
Would this sort of thing work for types with their own nested collections? Say as an example you had a bunch of products with categories you wanted to mass update. Would something along these lines work?

<form method="post" action="/Home/UpdateProducts">

<input type="hidden" name="products.Index" value="0" />
<input name="products[0].Name" value="Beer" />
<input name="products[0].Cataogry[0]" value="Drink" />
<input name="products[0].Cataogry[1]" value="Alcoholic" />

<input type="hidden" name="products.Index" value="1" />
<input name="products[1].Name" value="Chips" />
<input name="products[1].Cataogry[0]" value="Food" />

<input type="hidden" name="products.Index" value="2" />
<input name="products[2].Name" value="Salsa" />
<input name="products[2].Cataogry[0]" value="Food" />
<input name="products[2].Cataogry[1]" value="Condiment" />

<input type="submit" value="Submit Query" />
</form>
Requesting Gravatar... Vladan Strigo Oct 27, 2008 1:39 AM
# re: Model Binding To A List
@Ricky,

Your doing exactly the same thing I am. The only difference is that your custom attribute *is* your binder (implements binder interface and inherits from custom binder attribute) as well (which IMO from SRP perspective is not a good thing).

Here is my implementation (you will notice the issue I am having in the Binder itself):

akuaproject.googlecode.com/.../...BindAttribute.cs

akuaproject.googlecode.com/.../...cherAndBinder.cs

Notice:

var hackedBindingContext = _Recreate_Binding_Context_Because_of_SEALED_BindAttribute_in_Beta(bindingContext);

in BindModel and the function itself from above:

private ModelBindingContext _Recreate_Binding_Context_Because_of_SEALED_BindAttribute_in_Beta(ModelBindingContext currentBindingContext)
{
return new ModelBindingContext(currentBindingContext,
currentBindingContext.ValueProvider,
currentBindingContext.ModelType,
_GetPrefixOrModelName(currentBindingContext.ModelName),
() => currentBindingContext.Model,
currentBindingContext.ModelState,
_IsPropertyAllowed);
}
private string _GetPrefixOrModelName(string currentModelName)
{
return (string.IsNullOrEmpty(_prefix)) ? currentModelName : _prefix;
}
private bool _IsPropertyAllowed(string propertyName)
{
var includeProperties = StringHelper.SplitString(_includeProperties);
var excludeProperties = StringHelper.SplitString(_excludeProperties);

// We allow a property to be bound if its both in the include list AND not in the exclude list.
// An empty include list implies all properties are allowed.
// An empty exclude list implies no properties are disallowed.
bool includeProperty = (includeProperties == null) || (includeProperties.Length == 0) || includeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
bool excludeProperty = (excludeProperties != null) && excludeProperties.Contains(propertyName, StringComparer.OrdinalIgnoreCase);
return includeProperty && !excludeProperty;
}


This would be not needed if the BindAttr was not sealed.


Any thoughts?


Vladan
Requesting Gravatar... Brett Nov 12, 2008 3:38 PM
# re: Model Binding To A List
Can you add the Include / Exclude to the list?
Getting the stack overflow on my list. Assuming its because of nested objects of same type? So i want to restrict which properties it sets.

So in your example:

public ActionResult UpdateProducts(
[Bind(Include = "Name, Price")]IList products) {
return View(products);
}
Requesting Gravatar... haacked Nov 12, 2008 3:53 PM
# re: Model Binding To A List
@Brett Yes you can do it.
Requesting Gravatar... Trevor Nov 13, 2008 6:36 AM
# re: Model Binding To A List
No matter what I do, when I try this method I get a stack overflow exception. Seems it finds an infinite loop somewhere. Is there an update to this that I am perhaps missing?

Thanks,

Trevor
Requesting Gravatar... Brett Nov 13, 2008 7:25 AM
# re: Model Binding To A List
@Trevor

Me too. Trying to figure out whats causing it.
I noticed this:
www.codeplex.com/.../View.aspx?WorkItemId=2540

Is there a work around example we can get? Even with Bind include narrowing down my list it still causes it.

Thanks.
Requesting Gravatar... Brett Nov 13, 2008 10:00 AM
# re: Model Binding To A List
Ok I found solution to our problem. I guess the Bind(include doesnt work on the list. You have to place it directly on the object class thats in your list. So in his example.

public ActionResult UpdateProducts(
IList products) {
return View(products);
}

[Bind(Include = "Name, Price")]
public class Product {
public string Name { get; set; }
public decimal Price { get; set; }
}

Now i dont get stack overflow. Hope it helps someone.
Brett
Requesting Gravatar... Trevor Nov 13, 2008 11:30 AM
# re: Model Binding To A List
@Brett

Thanks! That was a big help. Although it only worked when I had a generic list, so instead of:

public ActionResult UpdateProducts(IList products)

I have:

public ActionResult UpdateProducts(IList products)

Otherwise I just get an empty list.

Requesting Gravatar... Trevor Nov 13, 2008 11:31 AM
# re: Model Binding To A List
whoops, I mean:

public ActionResult UpdateProducts(IList products)

could have sworn I typed that, but it didn't go in the comments
Requesting Gravatar... Trevor Nov 13, 2008 11:33 AM
# re: Model Binding To A List
OK. This is weird. I guess the editor doesn't like angle brackets:

public ActionResult UpdateProducts(IList<Product> products)
Requesting Gravatar... Hassan Nov 19, 2008 2:00 AM
# re: Model Binding To A List
How about if i use a HTML.ListBox that submit a List of EntityID and EntityName? Is there any way to do this with DefaultModelBinder without registering Custom Model Binder?
Requesting Gravatar... KevinUK Nov 26, 2008 8:58 AM
# re: Model Binding To A List
I am trying to do similar to what Joshua has suggested.

Lets say in the save method I am just expecting a single Product but one of it's properties is an IList of categories and each category has several properties.

My edit form displays the product and loops through each category displaying each category. When I come to save the form, Product.Category is null when I expect 2 items in the collection!

<%=Html.Hidden("product.Index",i) %>
<%=Html.Hidden("product.Category["+i+"].Id", cat.Id)%>
<%=Html.TextArea("product.Category["+i+"].Name", cat.Name)%>


HTML rendered:

<input id="product.Index" name="product.Index" type="hidden" value="0" />
<input id="product.Category[0].Id" name="product.Category[0].Id" type="hidden" value="1" />
<textarea cols="20" id="product.Category[0].Name" name="product.Category[0].Name" rows="2">


I can make this work if I pass through an extra parameter of IList category and change the above code accordingly but why won't this code work?

Thanks!
Requesting Gravatar... KevinUK Dec 01, 2008 2:46 AM
# re: Model Binding To A List
I have fixed my issue. This line needed correcting to:

<%=Html.Hidden("product.Category.Index",i) %>
Requesting Gravatar... jowen81 Dec 08, 2008 2:24 PM
# re: Model Binding To A List
I was having an issue with the DefaultModelBinder in the Beta 1 release when using Linq to SQL objects and the object contained an EntitySet<> property. The ModelState property would remain valid, but the entity set would never get updated. Today I discovered what was actually happening, the DefaultModelBinder was adding values directly to the entity set and then setting the entity set reference to itself - which for some reason leaves no entities in the set.

Pseudo Code of what's happening in the DefaultModelBinder:

var list = BusinessObject.ObjectList;
list.add(new {});
...
BusinessObject.ObjectList = list;


To fix the problem, I extended the DefaultModelBinder's BindModelCore method and added the additional check to see if the bindingContext.Model and the propertyResult.Value both contained a reference to the same object.

if (propertyResult.Value != null)
{
if (!property.IsReadOnly
// don't set if the property was updated directly
// E.g. elements added directly to a collection reference.
&& !ReferenceEquals(property.GetValue(bindingContext.Model), propertyResult.Value))
{
shouldCallSetValue = true;
}
}
else
{
...


Is anyone else having this problem? Is there a better way to handle this?
Requesting Gravatar... Graham Dec 14, 2008 10:37 PM
# re: Model Binding To A List
Thank you Phil!
Absolutely made my day.
Requesting Gravatar... Max Jan 02, 2009 12:39 AM
# re: Model Binding To A List
Very interesting feature, however it has its flaws.

Be sure not to use the Html.Hidden Method to generate the hidden inputs for the Index, as it will cause a NullReferenceException if the form is resubmitted after validation. See http://forums.asp.net/t/1344517.aspx for more details.

Has anyone a working example of the question that Joshua asked? Is this actually possible at this moment or will it be possible in future versions of ASP.NET MVC?
Requesting Gravatar... justin Jan 06, 2009 10:19 PM
# re: Model Binding To A List
I just posted my take on doing variable list binding here:

justsimplecode.com/.../...ists-in-asp.net-mvc.aspx

please let me know what you think, thanks.
Requesting Gravatar... jason Jan 08, 2009 3:17 AM
# re: Model Binding To A List
My prediction: Asp.Net Mvc will be just as bloated as web-forms, and twice the amount of "hacks" to get it running.
Requesting Gravatar... Jonas Jan 14, 2009 3:16 AM
# re: Model Binding To A List
Hi,

Very nice but I can't get the Action to return the class I specified, even though the data is available in the Request.Form collection, and also accessible if I use following method signature:

public ActionResult Edit(FormCollection items)

I would like it to look like this but then items returns null.

public ActionResult Edit(IList<MyItems> items)

Any suggestions?

Brgds

Jonas
Requesting Gravatar... Rich Urwin Jan 25, 2009 7:07 AM
# re: Model Binding To A List
Hi,

Is there any way of passing custom parameters / delegates to a ModelBinder?

The reason is, I'm using DevExpres's XPO ORM which (like most other ORMSs) uses sessions for object retrieval. If I want to set an XPO object as a property on another XPO object, I can create my custom binders to create the property/retrieve it from the DB, however, when doing this, I need to use the same ORM session as the parent object to avoid session mixing exceptions - but I can't obtain this session from anywhere because you can't (as far as I can tell) pass custom parameters into the ModelBinders.

The only way I can see to do this would be to either augment the binding mechanism to pass it through when calling UpdateModel from the controller, or better still, augment it to pass in a delegate to do the retrieval.

These are both rather messy and ideally any session-related code should stay in the data-access layer, but I can't see a better way.

Anyone got any ideas?
Rich
Requesting Gravatar... Bal Jan 28, 2009 8:14 AM
# re: Model Binding To A List
Is this feature still supported in the release candidate? I had it working in MVC Beta but after upgrading, it does not work. The collection is simply null in the action method.
Requesting Gravatar... Dan Miser Jan 28, 2009 8:39 AM
# re: Model Binding To A List
Seconded on Bal's observation. I upgraded my Beta site to RC1, and the binding on the list no longer works.
Requesting Gravatar... Dan Miser Jan 28, 2009 2:14 PM
# re: Model Binding To A List
My post on the asp.net mvc forums hasn't shown up yet. It appears the change that was made for RC1 was that the indexing of elements starts at zero, and counts up. At least that's my first take rummaging through the source.
Requesting Gravatar... Bal Feb 09, 2009 7:06 AM
# re: Model Binding To A List
yes, for anybody else thats comes accross this, your indexing should start from zero and be sequential in RC1. In beta the only requirement was that the index was consistent between related elements.
Requesting Gravatar... Carlos Sobrinho Feb 14, 2009 2:10 PM
# re: Model Binding To A List
Hi there,

I'm currently writing a custom ModelBinder for ISet<> collections.
I would like to make UpdateModel update my ISet<> models.

When I pass a entity Id < 0, it should create a new one.

My problem is, is it possible to access the string[] includeProperties, string[] excludeProperties passed in the UpdateModel inside the ModelBinder?

I need to insure that only some properties should be updated and I won't want to do: "entity[2].Id", "entity[2].Code", etc because I don't know if the index is 2 or some other value.

Rather, I think "entity.Id", "entity.Code" or even better "entity[].Id", "entity[].Code" would be great for specifying the include and exclude properties for collections.

What do you think? Any pointer on how to achieve this?
Requesting Gravatar... Huggy Mar 05, 2009 4:03 AM
# re: Model Binding To A List
As jowen81 implied at
haacked.com/.../model-binding-to-a-list.aspx#70052

DefaultModelBinder does not automatically bind the form values to EntitySet<Object> from Linq-to-Sql whereas it can do it on List<Object> collection.

Are there any shortcut to make this working?
Requesting Gravatar... faffy fuck Mar 16, 2009 6:50 AM
# re: Model Binding To A List
We tried this here at work a little different, however it backfired and now someone here has a repeater stuck up his pooper. Now what. Can anyone help?
Requesting Gravatar... Jose Marmolejos Jan 28, 2010 9:11 AM
# re: Model Binding To A List
Thanks for the tip man, it really simplified some tasks I was doing manually and saying WTF am i doing everytime I had to. The only thing is that I think you should update your article to reflect what is being pointed out in this comment: haacked.com/.../model-binding-to-a-list.aspx#70815
Requesting Gravatar... MIkael Syska Jan 28, 2010 7:49 PM
# re: Model Binding To A List
Hi,

Are there any news here? What about asp.net mvc 2 ?

mvh
Requesting Gravatar... dario-g Feb 24, 2010 1:44 PM
# re: Model Binding To A List
Yeap. I'm waiting for the same :) Phill, can you post something about changes in mvc 2?
Requesting Gravatar... Robert Koritnik Mar 09, 2010 4:55 AM
# re: Model Binding To A List
Anyone asking about MVC 2 here's the answer. I'm working on an app that uses Asp.net MVC 2 RC2 and the BEST thing is, this technique still works as in previous version. I'm able to bind complex objects to a List<T>.
Requesting Gravatar... Tyler Mar 20, 2010 11:45 PM
# re: Model Binding To A List
I upgraded to MVC RC2, and it looks like binding using arbitrary indexes is broken (your example above being "products[cold].Name"). You now HAVE to bind in sequential order, starting from 0. Was that an intentional change? It broke several of my forms.
Requesting Gravatar... haacked Apr 08, 2010 1:21 PM
# re: Model Binding To A List
@Tyler, that was an intentional change. But ASP.NET MVC 2 has brought that behavior back.
Requesting Gravatar... Andreas May 13, 2010 11:20 AM
# re: Model Binding To A List
Hi,

Nice article and great examples.

I have a question for all of you.

The "EditorTemplate And ViewModel" example demonstrates how /HiddenIndexerInputForModel/ makes it possible to model bind a non sequential collection of rows, thus allowing for javascript row deletion. That is great.

But how would you implement a javascript add?

A straight forward solution could look something like this:

$("#addRowLink").livequery('click', function (event) {
event.preventDefault();
var guid = jQuery.Guid.New();
var row = '<tr><td><input type="text" value="" name="Books[' + guid + '].Title" id="Books_' + guid + '__Title">.......';
$(".mytable tbody").prepend(newRowHtml);
}

Here I use a guid as the index value; ensuring the newly added row can be uniquely identified.

This solution works, but it seems smelly that the javascript contains a html string, that will have to be updated if the view or viewmodel structure changes.

Is there a better way to do this - e.g. generating a html-string variable on the server side?




Requesting Gravatar... Michael May 25, 2010 4:55 AM
# re: Model Binding To A List
I see that this article is pretty old, is this still the best solution or do MVC2 provide some new features.

Michael
Requesting Gravatar... Adam Jun 13, 2010 1:20 AM
# re: Model Binding To A List
Oi Haack!

How come TryUpdateModel doesn't bind to a list?

e.g.

var stuffs = _db.Stuff.Where(p => knownIds.Contains(p.stuffId)).OrderBy(p => p.stuffId);

if (TryUpdateModel(stuffs)) {

// Updated objects, not new ones!
}
else {
}

I don't need saving from myself...

Also! Wouldn't the metadata model would be better if you had a metadata register. Then you don't (always) need to use data-annotations / attributes when using strongly typed helpers.

e.g.

MetaData.MataDataValues[typeof(Chilli)]["HotnessRating"] = "Hotness Rating";

Or using one of those dynamic doo-dars perhaps for people who don't like straight squared backets.

MetaData.MataDataValues[typeof(Chilli)].HotnessRating = "Hotness Rating";

Don't get me wrong guys if you like Data Annotations, great. If you use IDataErrorInfo and you've got some wierd validation going on, the above would work great too and it's tidy. Kinda looks like how ModelBinder dictionaries work.

Also!

HtmlHelper is aweful. Most of the time you have to type a gazillion dots just to get basic things you need. Like the HttpContextBase or HttpRequestBase. The C# monks want to burn me at stake for dis-obeying the commandment of demiter.

If you could get at querystring variables through the RouteData that'd be quite good.

HtmlHelper<T> is also aweful. If you want to write expression based helpers you have to go all over the place to the job. Use some static expression helper, then some metadata or something. How come this isn't all in the same place since it's all related.

Mr Phil, you're normally meticulous, but a few small parts of MVC2 seem like they've been haacked together. What went wrong? I'm sure it was probably Brad's fault.
Requesting Gravatar... helal Jul 22, 2010 11:16 PM
# re: Model Binding To A List
Can we use a 2 dimensional array to bind it in our form?
In my model i have this:
public class EmailDetails
{
public string[] emailTo { get; set; }
public string[,] emailFieldValues { get; set; }

}

so in my form to bind the emailTo is simple and it works:
<input type="text" id="Text1" title=" " name="emailTo" maxlength="100" class="required email txt userInput" />

I need a 2 dimensional array so that i can match the value with something.

Is that possible?
Requesting Gravatar... George Aug 11, 2010 11:22 PM
# re: Model Binding To A List
What if you're wanting to display a list of options outside the scope of the Model you're representing, ie. a MultiSelectList assigning List<Interest> to User.Interests. Simply passing User.Interests to the control leaves it unaware of anything other than that User's selection, and passing it List<Interest> leaves it unaware of the users selection!
Requesting Gravatar... James Aug 21, 2010 7:37 AM
# re: Model Binding To A List
Thanks for the tips Phil. I realise this is a very old post, but it still comes up in Google, so I thought I'd throw in some help for anyone who is maybe doing what I'm doing.

Here is a function that will serialize a javascript object or array to a form data representation so that it can be bound by MVC model binders as you describe:


function encodeData( data, prefix )
{
var ret = "";
var pre = prefix ? prefix : "";

if( data.constructor === Array )
{
for( index in data )
{
ret += encodeData(data[index], pre+'['+index+']');
}
}
else if( data.constructor === Object )
{
if( pre != "" ) pre += '.';

for( key in data )
{
ret += encodeData(data[key], pre+key);
}
}
else return encodeURIComponent(pre)+'='+encodeURIComponent(data)+'&';

return ret;
}
Requesting Gravatar... Serhiy Aug 22, 2010 2:25 AM
# re: Model Binding To A List
Help me please! My compiler said that syntax is invalid here "<%: ". It really don't like ":" after "<%". How can I fix this? Thank you.
Requesting Gravatar... Cory Fowler Sep 01, 2010 8:14 AM
# re: Model Binding To A List

I'm trying to get this scenario to work using jQuery.



I'm using $('#myForm').serialize(); to serialize the form which is formatting the values like this 'ints=1&ints=4&ints=2&ints=8'.



To perform the asynchronous action I am using jQuery.ajax ($.ajax) which looks like this:




$.ajax({
type: 'GET', // I'm retrieving a list of values for update.
cache: false,
url: url,
dataType: 'json',
data: $('#myForm').serialize(),
success: MarkSelectedCheckboxes(data)
});


the signature for my controller action looks like this:




public ActionResult GetCurrentlyCheckedBoxes(ICollection<string> checkBoxesInView, ICollection<string> CheckedBoxesInView, string RecordIdentifier)


I don't seem to be getting the results back in an expected manner. Any thoughts? Thanks in Advance.

Requesting Gravatar... Cory Fowler Sep 01, 2010 9:52 AM
# re: Model Binding To A List
My post is a prime example of how over architecture can lead to very complex solutions, I opted to return a full result set based solely on the RecordIdentifier and don't have any more issues.

Thanks,

Cory

What do you have to say?

(will show your gravatar)
Please add 1 and 4 and type the answer here: