May 21

In the last section of my MVC Templates post, I discussed how to use the Html.EditorFor() helper with complex types. Whilst this showed how you can use Html.EditorFor() and templates to produce a user interface for complex types, it didn’t show you how to bind the result when the user has posted to a controllers action. In this post I will extend the employee example, so that it displays the offices the the user selected.

 

Download the code for this example

Default Model Binders

The concept of Model Binders was introduced in MVC Preview 5, before Model Binders in a controllers action we would either get values directly from the Request Context, for example Request[“Foo”] or bind directly to the Action’s parameters, for example public ActionResult Foo(string Bar). This is OK but can get untidy if you have more than a couple of Model properties to bind. Based on the request context the DefaultModelBinder will bind our Model’s properties for us, hence when creating templates it’s important to understand how to create the correct markup for your input fields using TemplateInfo. The supported types for the DefaultModelBinder are Primitive types, such as String etc, Model classes, and generic collections List<T> etc.

Before I discuss Model Binders, lets examine what happens if we select Offices using the DefaultModelBinder. Debug\Employee\Index provide a First Name, Last Name, and Office then click on the Create button, you will see the following validation message:

Offices using DefaultBinder

DefaultBinder

The reason for this is that the DefaultBinder doesn’t know how to bind our List<Office> from a Select input field.

I Thought the DefaultBinder Supported Collections?

Yes, but only in a specific way, I’ll discuss a Alternative Approach later in the post. For this example our requirements are to bind using a Select input field, in my opinion creating a simple Custom Model Binder is simplest way to do this.

Custom Model Binders

Below are the changes required to add a Custom Model Binder to my previous complex types example.

First of all a little house keeping, we need to remove the Office List from the Index action into it’s own method and class. In a real situation you would get the data from a infrastructure service such as a database.

Paste this standalone class into EmployeeModel.cs

    //TODO This should be replaced by a infrastructure service such as a database
    public static class ModelData {
        public static List OfficeData = new List(new Office[]{
                                                new Office{ OfficeId = new Guid("ee70f79e-3840-4e8f-98eb-aa8ee12e254c"), Address = "One Microsoft Way, Redmond", Country = "United States", PostalCode = "WA 98052" },
                                                new Office{ OfficeId = new Guid("0780b91a-7000-4860-a89c-ed6a1ea6c071"), Address = "Microsoft Campus, Thames Valley Park, Reading, Berkshire", Country = "United Kingdom", PostalCode = "RG6 1WG" },
                                                new Office{ OfficeId = new Guid("db7a5076-c7c7-42f3-a6c6-1ead82af310a"), Address = "Centro Direzionale San Felice, Palazzo A. Via Rivoltana, 13", Country = "Italy", PostalCode = "1320090 Segrate (MI)" }
                                            }
                                        );

    }

Next we need to create our own Model Binder, create a folder called Binders, add a new class and name it EmployeeBinder.cs.

Add Model Binder Class

Add Model Binder Class

Then paste the code below into your new class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using MvcTempDemo1.Models;

namespace MvcTempDemo1.Binders {
    public class EmployeeBinder : DefaultModelBinder {

        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {

            var model = base.BindModel(controllerContext, bindingContext) as EmployeeModel; //Bind most of the model using the built-in binder, this will bind all primitives for us 

            const string magicString = "Offices";  //TODO Use of magic strings

            var offices = bindingContext.ValueProvider.GetValue(magicString); //Get the posted value for offices

            if (offices != null && !string.IsNullOrEmpty(offices.AttemptedValue)) { //Check we have a value for offices before we proceed

                bindingContext.ModelState.Remove(magicString); //Remove binding conversion errors for offices, as we are going to deal with binding offices ourselves

                try {
                    //Create a list of offices based on the comma delimited string of posted OfficeId's
                    model.Offices = new List(
                                                        offices.AttemptedValue.Split(",".ToCharArray())
                                                        .Select(id => new Office() { OfficeId = new Guid(id) })
                                                        .ToList()
                                                    );
                }
                catch (FormatException ex) { //Catch if the posted offices are not posted as a comma delimited string of Guids
                    bindingContext.ModelState.AddModelError(magicString, ex); //Add an error to the model state, used for ModelState.IsValid and Html error helpers
                }
                catch (Exception ex) { //Unexpected exception
                    throw ex;
                }
            }

            return model;
        }

    }
}
How Does This Work?

We only want to do our own model binding for the Offices model property which is the type of List<Office>, for everything else primitive string’s, DateTime’s etc we want to use default model binding.

Therefore we inherit the DefaultModelBinder, as it would be silly to reinvent the wheel when the default does most of what we want.

Next we call base.BindModel this does a all the Default Binding for us and returns an object representing our model, we then convert the bound object to our EmployeeModel using the as keyword.

We now have a bound EmployeeModel, now we need to add some code to do our custom binding for the Offices property.

First we need to get the posted value for Offices, the preferred way is via ValueProvider.GetValue, rather then using the Request Context directly for example Request[“Foo”].

Next we check that a value was posted for Offices.

Then we remove any ModelState errors for Offices, as we will be binding Offices ourselves (there will be conversion errors from the call to base.BindModel).

We know that the Offices value will be posted as an encoded set of Office id’s with multiple values delimited by a commas. All we do is split the string value will got via ValueProvider.GetValue and create a new List<Office> using just the posted Office id’s, we then set the model.Offices property to this List.

We catch any FormatExceptions which occur, for example if an id was posted that was not the type of GUID, the exception is added to ModelState for use in ModelState.IsValid and Html helpers. Any unexpected exceptions are thrown.

Finally we return the Model, hopefully with the Offices model property populated with the ids of the Offices the user selected.

How to Wire-Up a Custom Model Binder?

For a particular model the easiest way to tell the MVC framework to use your custom model binder is by decorating the model with the ModelBinder attribute. The ModelBinder attribute takes one parameter the type of your model binder. The ModelBinder attribute adds your ModelBinder to the ModelBinderDictionary. You can also add your to your model to the ModelBinderDictionary via the Application_Start() event in the Global.asax file. I prefer the former method as it is more obvious what’s going on and feels more like convention over configuration.

To wire up our custom model binder, lets add the ModelBinder attribute to our EmployeeModel. Change the EmployeeModel class in Models\EmployeeModel.cs:

    [ModelBinder(typeof(EmployeeBinder))]
    public class EmployeeModel {
        [Required]
        [DisplayName("First Name")]
        public string FirstName { get; set; }

        [Required]
        [DisplayName("Last Name")]
        public string LastName { get; set; }

        [DisplayName("Date of Birth")]
        public DateTime? DateOfBirth { get; set; }

        [DataType("Offices")]
        public List Offices { get; set; }
    }
Change the Details Action

We need to make some simple changes to the EmployeeController’s Details action. In Controllers\EmployeeController.cs change the Details action as follows:

	[HttpPost]
        public ActionResult Details(EmployeeModel model) {
            if (ModelState.IsValid) {
                //TODO This should be replaced by a infrastructure service such as a database
                if (model.Offices != null) {
                    var offices = new List();
                    //Get offices by OfficeId that where selected
                    foreach (var office in model.Offices) {
                        offices.Add(
                                        ModelData
                                        .OfficeData
                                        .Single(o => o.OfficeId == office.OfficeId)
                                    );
                    }
                    model.Offices = offices;
                }
                //

                return View(model);
            }
            else {
                return RedirectToAction("Index", model);
            }
        }

This change just selects the Office by it’s id using the model which was bound using our custom model binder. In a real scenario this would be a call to an infrastructure service such as a database.

Create a Display Template

Add a shared display template for Offices, if you don’t know how to do this please read my previous post. Then add the following code to \Views\Shared\DisplayTemplates\Offices.ascx:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<List<MvcTempDemo1.Models.Office>>" %>

<%=Html.LabelFor(model => model)%>
<%=Html.Encode(string.Join("; ", Model.Select(model => model.Country).ToArray()))%>

Finally in the Details view add the DisplayFor helper for Offices. Paste the following code into Views\Employee\Details.aspx:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcTempDemo1.Models.EmployeeModel>" %>

	Details




Details

Fields <%=Html.DisplayFor(model => model.FirstName)%> <%=Html.DisplayFor(model => model.LastName)%> <%=Html.DisplayFor(model => model.DateOfBirth)%> <%=Html.DisplayFor(model => model.Offices)%>
<%= Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> | <%= Html.ActionLink("Back to List", "Index") %>

Debug\Employee\Index provide a First Name, Last Name and Office then click on the Create button.

Offices DisplayFor

Offices DisplayFor

As you can see the Office selection is now populated with the rest of the information.

Custom Model Binders Best Practice

Just a quick word on best practice for Custom Model Binders, the only function of a Model Binder is to bind the HTTP request data to the Model and add any validation errors to the ModelState. It should be as lightweight as possible, it shouldn’t call any infrastructure services such as a database or web service, it should deal with binding and only binding. I’ve seen some examples of people calling out to databases via a custom model binder, this approach will: add dependencies, make it harder to unit test and breaks separation of concerns. Imagine if the DefaultModelBinder was dependent on a database :)

Improvements

Apart from the obvious missing data service, the use of a magic string in the Custom Model Binder makes the code a little bit fragile, a simple change would be to select the property by type. This would mean you could re-use the Model Binder for any Model that has List<Office> type regardless of what the property name is. In summary using magic strings means you are binding using the property name rather than the type, therefore losing the opportunity of code reuse by type and affecting the way you would unit test the Model Binder.

Alternative Approach

As mentioned binding to Lists and Collections is supported, but it’s a little bit specific, namely you need to use hidden fields/textboxes, Phil Haack has written an excellent post on this subject. Whilst you could get the DefaultBinder to work with a select field, a custom Model Binder in my opinion is a more elegant approach.

Conclusion

One of the reasons the MVC framework is brilliant is – if it doesn’t quite do what you require just extend the default behavior. I hope this post demonstrated how easy it is to create your own Model Binder and that in the MVC world you rarely need to compromise on your Models or User interfaces because if it doesn’t work for you just extend the functionality.

For more tips on Model Binders see Scott Allen’s excellent post.

http://msdn.microsoft.com/en-us/library/system.web.mvc.modelbinderdictionary_members.aspx

10 Responses to “ASP.NET MVC Model Binders”

  1. Methods says:

    good information, you write it very clean. I am very lucky to get this tips from you.

  2. Carlos says:

    Beautiful code and explanation. Thanks for the hard work! ;)

  3. […] nasty hacks. In this post we are going to continue our Employee example from my MVC Templates and MVC Model Binders posts, we will change the code to make use of the new […]

  4. Kyle Russell says:

    Could you post an example of the improvement you proposed by not using the magic string? I’m curious how that would work. Finding decent articles about things like model binders is not easy. Your posts have been extremely helpful. Thanks

  5. RCM says:

    Great article. In the custom model binder, is there a way to re-validate the model after you update it with the List of Offices? Say your Offices model had DataAnnotations on it. Is there a way to validate the model and have it update the ModelState with any errors?

  6. Andrew Knox says:

    Thank you. Finally a decent article on the basics model binding (well, I’d not come across any at least!), helped me out a lot :)

  7. Scott says:

    Thanks for this – very concise. I’m stuck at an architectural challenge, that you touch on in your Best Practice section. In real life, the Offices are out in a database somewhere, so how do you go fetch the appropriate record in a ModelBinder.
    I agree that the ModelBinder should not be calling directly to the database, but the only other option I can see is to add all the Offices into the ViewBag (or similar) and iterate through them via the controllerContext.
    But that seems quite expensive, especially if you had 43,000 offices…

    • DalSoft says:

      You shouldn’t connect to the database via a model binder instead get the data from the database in the controllers action. The model binder is for binding the http request to your model, from the controllers action you then use the model values to query your database. If your worried about performance consider system.web.caching or output caching, if you have the choice go for output caching

  8. Jarrett says:

    I echo Andrew Knox here in saying finally, a decent post on how to easily extend the default model binder.

  9. Mark says:

    It’s worth noting that you can also use the ModelBinder attribute on an controller’s action parameter, which is useful if the custom ModelBinder is required only for certain actions.

Leave a Reply

preload preload preload