Model binding a collection with MVC and .NET 2.0

After about 3 hours of frustrating debugging, I came across a quirk/bug when using:

  • MVC
  • .NET 2.0 / 3.5
  • Model binding a collection

Imagine you have a collection of, say User objects.  And you would like to bind that collection to your view, but you only want to display some of the users.  For example, only admin users.  Your view would look something like this..

for (int i=0; i < Model.MyUsers.Count; i++)
{
    User user = Model.MyUsers[i];

    if(user.IsAdmin)
    {
        @Html.EditorFor(user)
    }
}

Now in .NET 4.0, your collection of “admin” users automatically gets bound to a collection in your controller. However, if you run your MVC application on .NET 2.0 framework (which includes 3.5 applications), your model binding will break once it gets to your controller (specifically, your MyUsers collection will be null)!

Why?

MVC uses an index notation in the name attribute of an html element to perform model binding when the request is submitted.  You can inspect the DOM to see this.

Let’s say you have a collection of three User objects, that you render into a <ul>, where each <li> contains a text box that allows someone to edit each user’s FirstName.  MVC will render the following HTML.

<ul>
    <li>
        <input type="textbox" name="MyUsers[0].FirstName" id="MyUsers_0_FirstName" value="Peter" />
    </li>
    <li>
        <input type="textbox" name="MyUsers[0].FirstName" id="MyUsers_0_FirstName" value="Brian" />
    </li>
    <li>
        <input type="textbox" name="MyUsers[0].FirstName" id="MyUsers_0_FirstName" value="Stewie" />
    </li>
</ul>

If you wanted to exclude “Peter” for some reason (i.e. he’s not an “admin” user) MVC will render HTML with Brian’s and Stewie’s objects indexed by 0 and 1, respectively. And when it binds on the request, your controller will receive a collection containing only two objects (Brian and Stewie).

However, .NET 2.0 does this a little differently. It will actually render the Brian and Stewie objects indexed by 1 and 2 (because that is their index in the original, non-filtered collection).

This makes model binding break on the request. Your controller will have a null collection!

The Solution

Upgrade to 4.0! (no, just kidding, I know this isn’t always possible). If you can’t run on 4.0, and you need a work-around, you can manually manipulate the HTML field name that MVC generates for your collection:

for (int i=0; i < Model.MyUsers; i++)
{
    int renderedUserCounter = 0;

    User user = Model.MyUsers[i];

    if(user.IsAdmin)
    {
        string htmlFieldName = string.Form("MyUsers[{0}]", renderedUserCounter);
        renderedUserCounter++;

        @Html.EditorFor(m => Model.MyUsers[i])
    }
}

Your Thoughts?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s