find a location for property in a new city

Wednesday, 6 July 2011

Custom Model Binder for binding drop downs into a DateTime in MVC

I have a Nullable DateTime Property in my Model I want to bind painlessly with three drop down select boxes I have for Day Month and Year respectively.

First thing to do is to make a new shiny custom model binder. I have done mine by inheriting from DefaultModelBinder (which you will need to include System.Web.Mvc to access).
using System;
using System.ComponentModel;
using System.Web.Mvc;

namespace Infrastructure.OFP.ModelBinders
{
    public class MyUserModelBinder : DefaultModelBinder
    {
        protected override void BindProperty(ControllerContext controllerContext,
            ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
        {
            if (propertyDescriptor.PropertyType == typeof(DateTime?)
                && propertyDescriptor.Name == "DateOfBirth")
            {
                var request = controllerContext.HttpContext.Request;
                var prefix = propertyDescriptor.Name;
    
//remember I am a Brit so this will give me dd/mm/yyyy format - this may not work for you :)
//also since this is a binder specific for one particular form the hardcoding of
//    MyUser.DateOfBirth is suitable
//AND... I am using Value because I am using a Nullable
                var date = string.Format("{0}/{1}/{2}",
                           request["MyUser.DateOfBirth.Value.Day"],
                           request["MyUser.DateOfBirth.Value.Month"],
                           request["MyUser.DateOfBirth.Value.Year"]);

                DateTime dateOfBirth;
                if (DateTime.TryParse(date, out dateOfBirth))
                {
                    SetProperty(controllerContext, bindingContext,
                                propertyDescriptor, dateOfBirth);
                    return;
                }
                else
                {
                    bindingContext.ModelState.AddModelError("DateOfBirth",
                           "Date was not recognised");
                    return;
                }
            }
            base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
        }
    }
}

This new Model Binder will need to be registered to be used when binding a MyUser object. You do this in the Global.asax Application_Start() like so:
protected void Application_Start()
{
    ModelBinders.Binders[typeof(MyUser)] = new MyUserModelBinder();
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
}

For reference the drop downs I created to populate the nullable DateTime property of DateOfBirth is as follows:
@Html.DropDownListFor(m => m.MyUser.DateOfBirth.Value.Day,
                      new SelectList(Model.Days, "Key", "Value"))
@Html.DropDownListFor(m => m.MyUser.DateOfBirth.Value.Month,
                      new SelectList(Model.Months, "Key", "Value"))
@Html.DropDownListFor(m => m.MyUser.DateOfBirth.Value.Year,
                      new SelectList(Model.Years, "Key", "Value"))
@Html.ValidationMessageFor(m => m.MyUser.DateOfBirth)

This works as I expected it to. It validates the property as expected using the DataAnnotations that I have defined for it.

Follow britishdev on Twitter

4 comments:

  1. Hello Colin,

    Any chance you can provide a complete example of your solution?

    Thanks,
    Andreas

    ReplyDelete
  2. Yep, complete example is what would help. Thank you,

    ReplyDelete
    Replies
    1. Admittedly the article is a little blunt and straight down to code, but what is it you feel is incomplete about the example exactly? Maybe I can elaborate. Although bear in mind I wrote this almost a year ago :)

      Delete
  3. This was extremely helpful Colin. Great work!

    ReplyDelete