Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

The DefaultModelBinder in ASP.NET MVC for N00bs

0.00/5 (No votes)
21 May 2015 1  
The DefaultModelBinder in ASP.NET MVC for N00bs

I once met a bright young person from a mysterious place where I think dragons might live. He asked: “Hey Camilo, when I attempt to bind my view model to the controller, I get a null.” In this article, I would like to take a tour through the DefaultModelBinder in ASP.NET MVC.

Turns out, there is a ton of amazing capability you get for free right out of the box.

Models

Before I begin, I would like to take a moment to talk about the models we’ll bind to in C#. Let’s begin with a Person that has a Friend and Addresses.

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string City { get; set; }
    public string State { get; set; }
}

public class PersonViewModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Person Friend { get; set; }
    public Address[] Addresses { get; set; }
}

This is a bit contrived since I hope a person has more than one friend. Since I hail from America, I’ve decided to stick with states.

You can think of Person and Address as tables in a database. I prefer to put models that get strongly typed to the view in view models. I like to think of view models as aggregates that sum up data.

This is what the controller looks like:

public class PersonController : Controller
{
    public ActionResult Edit()
    {
        var vm = new PersonViewModel
        {
            Id = 1,
            Name = "Jane Doe",
            Friend = new Person
            {
                Id = 2,
                Name = "Jon Doe"
            },
            Addresses = new Address[]
            {
                new Address
                {
                    Id = 1,
                    City = "Athens",
                    State = "Texas"
                },
                new Address
                {
                    Id = 2,
                    City = "Paris",
                    State = "Texas"
                }
            }
        };
        return View(vm);
    }

    [HttpPost]
    public ActionResult Edit(PersonViewModel vm)
    {
        return View("Details", vm);
    }
}

You might be wondering if Athens and Paris actually exist in Texas. Well they do, and are nice places to see. No dragon sightings there, I’m afraid.

You can begin to imagine how the default binder is going to handle the entire view model. I’ve written minimal code to deal with a wide array of data.

Binding to Complex Types

You can think of the model binder as the machine that churns an HTTP text message into a C# object.

To bind complex C# types, all we need in the Razor view is this:

@using (Html.BeginForm("Edit", "Person"))
{
    <div>
        @Html.HiddenFor(m => m.Id)
        <label>Name:</label>
        <span>@Html.TextBoxFor(m => m.Name)</span>
    </div>
    <div>
        <button>Submit</button>
    </div>
}

The HTML rendered is super simple:

<form action="/" method="post">
    <div>
        <input id="Id" name="Id" type="hidden" value="1" />
        <label>Name:</label>
        <span>
            <input id="Name" name="Name" type="text" value="Jane Doe" />
        </span>
    </div>
    <div>
        <button>Submit</button>
    </div>
</form>

The default binder takes the name attribute from the input tag to bind C# property objects. The value attribute sets the value. This is what the HTTP request body looks like when I click “Submit”:

Id=1&Name=Jane+Doe

I’d like you to think of HTTP requests as a key / value pairs. So, name points to the property name in C# and value to its value. It behaves the same as doing this in your controller:

var vm = new PersonViewModel();
vm.Id = 1;
vm.Name = "Jane Doe";

What is amazing is how the default binder takes the strong typing in C# into account. String types get string values, and integer types get integer values.

With me so far? Let’s go in a bit deeper.

Binding to Nested Types

Let’s pick up the pace a bit and move on to a nested type. All we need is:

<div>
    <label>Friend:</label>
    @Html.HiddenFor(m => m.Friend.Id)
    <span>@Html.TextBoxFor(m => m.Friend.Name)</span>
</div>

It looks awfully complicated. But, all the default binder sees is Friend.Name. So, it recurses through the C# objects to find the property. The entire engine is recursive in nature, so you can go as deep as you like. This will support complex datasets right out of the box.

If you go back to the controller, you’ll see PersonViewModel vm as the method parameter in Edit(). The default model binder sees this object graph when it binds the view model:

new PersonViewModel
{
    Friend = new Person
    {
        Id = 2,
        Name = "Jon Doe"
    }
};

It picks all that up from this HTML:

<div>
    <label>Friend:</label>
    <input id="Friend_Id" name="Friend.Id" type="hidden" value="2" />
    <span>
        <input id="Friend_Name"
            name="Friend.Name"
            type="text"
            value="Jon Doe" />
    </span>
</div>

As long as you map name attributes to the correct object property, the default model binder will handle the rest.

List Binding

The default binder needs a way to keep track of a list of objects. By convention, it uses an array-like syntax to do this. Let’s have a look:

for (var i = 0; i < Model.Addresses.Count(); i++)
{
    <div>
        @Html.HiddenFor(m => m.Addresses[i].Id)
        <label>City:</label>
        <span>@Html.TextBoxFor(m => m.Addresses[i].City)</span>
        <label>State:</label>
        <span>@Html.TextBoxFor(m => m.Addresses[i].State)</span>
    </div>
}

And this is the HTML:

<div>
    <input id="Addresses_0__Id"
        name="Addresses[0].Id"
        type="hidden" value="1" />
    <label>City:</label>
    <span>
        <input id="Addresses_0__City"
            name="Addresses[0].City"
            type="text"
            value="Athens" />
    </span>
    <label>State:</label>
    <span>
        <input id="Addresses_0__State"
            name="Addresses[0].State"
            type="text"
            value="Texas" />
    </span>
</div>
<div>
    <input id="Addresses_1__Id"
        name="Addresses[1].Id"
        type="hidden" value="2" />
    <label>City:</label>
    <span>
        <input id="Addresses_1__City"
            name="Addresses[1].City"
            type="text"
            value="Paris" />
    </span>
    <label>State:</label>
    <span>
        <input id="Addresses_1__State"
            name="Addresses[1].State"
            type="text"
            value="Texas" />
    </span>
</div>

Personally, I am not in favor of the wacky syntax. You may have noticed, if you go back to the view model, that I use an Addresses[] array to make the magic work. The default model binder gets to be picky about indexes. So, as long as you zero-index and increment consecutively, it will work. Just make sure there are no gaps in your indexes. There is sort of a hack to get around index gaps, but I don't recommend it since this is already weird enough.

Conclusion

It’s time for the grand finale! Let’s see the default model binder in action:

Default Binder in Action

And to geek out, here is the HTTP request body:

Id=1&Name=Jane+Doe&Friend.Id=2&Friend.Name=Jon+Doe&Addresses%5B0%5D.Id=1&
Addresses%5B0%5D.City=Athens&Addresses%5B0%5D.State=Texas&
Addresses%5B1%5D.Id=2&Addresses%5B1%5D.City=Paris&Addresses%5B1%5D.State=Texas

You may now go back to the original C# object I created in the controller to make sense of that blob of text. Feel free to admire the beauty of the default model binder. Yes, it is okay to shed a few tears.

If interested, you may find the entire demo up on GitHub.

The post The DefaultModelBinder in ASP.NET MVC for N00bs appeared first on BeautifulCoder.NET.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here