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 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:

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 housekeeping, 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 an 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

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) { // Bind most of the model using the built-in binder, this will bind all primitives for us var model = base.BindModel(controllerContext, bindingContext) as EmployeeModel; // TODO Use of magic strings const string magicString = "Offices"; // Get the posted value for offices var offices = bindingContext.ValueProvider.GetValue(magicString); // Check we have a value for offices before we proceed if (offices != null && !string.IsNullOrEmpty(offices.AttemptedValue)) { // Remove binding conversion errors for offices, as we are going to deal with binding offices ourselves bindingContext.ModelState.Remove(magicString); 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 if the posted offices are not posted as a comma delimited string of Guids catch (FormatException ex) { // Add an error to the model state, used for ModelState.IsValid and Html error helpers bindingContext.ModelState.AddModelError(magicString, ex); } 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 we 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 its 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>>" %> <div class="display-label"> <strong><%=Html.LabelFor(model => model)%></strong> </div> <div class="display-field"> <%=Html.Encode(string.Join("; ", Model.Select(model => model.Country).ToArray()))%> </div>

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>" %> <h2>Details</h2> <fieldset> <legend>Fields</legend> <%=Html.DisplayFor(model => model.FirstName)%> <%=Html.DisplayFor(model => model.LastName)%> <%=Html.DisplayFor(model => model.DateOfBirth)%> <%=Html.DisplayFor(model => model.Offices)%> </fieldset> <%= 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

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.