Let’s say that you have a model with a DateTime
property.
public class Person
{
//...
public DateTime DateOfBirth { get; set; }
}
Suppose that you want someone to fill out the details and post them back to your site. Simple enough: add a couple of action methods and then insert an EditorFor
in your view:
//controller
public ActionResult EditDetails()
{
return View(new Person()); //or loaded from DB, etc
}
[HttpPost]
public ActionResult SaveDetails(Person person)
{
//save updated person to DB
}
<!-- view -->
<div class="editor-field">
@Html.EditorFor(model => model.DateOfBirth)
</div>
That’s all you need for basic date editing, though for your users’ sake I suggest you look at a nicer editor than the plain input
that this will generate!
But what if you want to support some format besides the default? MVC doesn’t handle that out-of-the-box so you’ll need a custom model binder…
Custom Model Binder
The model binder is responsible for converting a series of form values into the rich, strongly-typed model that is passed as a parameter to your action method.
Thankfully there’s no need to re-implement all of the pretty-complex functionality involved in that process just to alter how dates are parsed; a custom model binder can be associated with a single type, and the rest of the binding functionality will work as normal.
Custom model binder implementations need to implement the IModelBinder
interface, but the easier approach is to inherit from System.Web.Mvc.DefaultModelBinder
and override the one method in which we are interested: BindModel
public class DateTimeModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// ???
}
}
The BindModel
method is responsible for returning the value of a model based on the controller- and the binding-context (which between them provide a lot of contextual information about the request), and our custom date parsing implementation will need to:
- get hold of the custom format that we want to use
- use that format with the value that has been posted back to the server
Specifying the Date Format
There are a lot of different ways in which you might determine or specify the custom format, but for the purposes of this example I am just going to pass it into the constructor.
public class DateTimeModelBinder : DefaultModelBinder
{
private string _customFormat;
public DateTimeModelBinder(string customFormat)
{
_customFormat = customFormat;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// ???
}
}
In a real implementation you might grab this from a data annotation attribute, from configuration, from the request information - wherever makes sense for your project.
Getting the POSTed Value
The bindingContext
that is passed to the BindModel
method gives us a couple of properties that we can use to get the POSTed value from the request:
ModelName
gives us the name of the property on which the binder is being usedValueProvider.GetValue(string)
will return an instance ofValueProviderResult
from which we can get the raw value
The ValueProvider.GetValue
method takes a “key” as the parameter, for which we can use the value of the ModelName
property:
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
value.AttemptedValue; //provides the raw value
From here it is a simple step to tie everything together with a DateTime.ParseExact
:
public class DateTimeModelBinder : DefaultModelBinder
{
private string _customFormat;
public DateTimeModelBinder(string customFormat)
{
_customFormat = customFormat;
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
return DateTime.ParseExact(value.AttemptedValue, _customFormat, CultureInfo.InvariantCulture);
}
}
Hooking it up
Now that we have the model binder, we need to associate it with all DateTime
properties throughout our application. We do this by adding an instance of the binder to the ModelBinders.Binders
collection from within Global.asax.cs.
protected void Application_Start()
{
//...
var binder = new DateTimeModelBinder(GetCustomDateFormat());
ModelBinders.Binders.Add(typeof(DateTime), binder);
ModelBinders.Binders.Add(typeof(DateTime?), binder);
}
This will associate our new binder with any properties or values of type DateTime
or DateTime?
. Now, whenever one of these types is encountered by MVC whilst trying to parse a POSTed value it will use our custom binder and therefore our custom format!