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.