Customizing model binder (localizing)

This article describes how to create custom model binders for ASP.NET MVC (view) models and why are they useful. In every use case I will also show example code.

In first case I used custom binder for localizing [Required] attribute. Many would ask why is that needed if this attribute by itself provides localization. That is basically true but in cases of native types (int, double,...) you might run into problems. Here is an example. If you have in your model a property of type int (also similar decimal, DateTime...) and would like to put [Required] attribute to it you would have problems since it is never null. It has it's default velue which is 0 for int. One solution is to make int optional like int? and put [Required] attribute on it. Another is to put [Range] attribute. In both cases you run into trouble. In first you will have problem that the value can be null (although you know it must be not null) and in second there is problem that you cannot predict allowed range which must be provided. This problem is easy to solve using custom binder. Here is the code of my binder:

public class ApplicationModelBinder : DefaultModelBinder
{
    protected override bool OnPropertyValidating(ControllerContext controllerContext, ModelBindingContext bindingContext,
        PropertyDescriptor propertyDescriptor, object value)
    {
        if ((propertyDescriptor.PropertyType == typeof(DateTime) && value == null) ||
            (propertyDescriptor.PropertyType == typeof(int) && value == null) ||
            (propertyDescriptor.PropertyType == typeof(decimal) && value == null) ||
            (propertyDescriptor.PropertyType == typeof(bool) && value == null))
        {
            var modelName = string.IsNullOrEmpty(bindingContext.ModelName) ? "" : bindingContext.ModelName + ".";
            var name = modelName + propertyDescriptor.Name;
            bindingContext.ModelState.AddModelError(name, General.RequiredField);
        }
        return base.OnPropertyValidating(controllerContext, bindingContext, propertyDescriptor, value);
    }
}

From the code you can see that I check for type of property in a model and then add a modal error. It is a localized string like "This field is required.". Since I want to use it on all models I have made it as default binder. This is achieved in Global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        ModelBinders.Binders.DefaultBinder = new ApplicationModelBinder();
    }
}

Next case when custom binders are very helpful is by decimal types. ASP.NET by default parse its value by locale set in browser. Some programmers solved that problem with simple trick. They made decimal properties strings. That means they parse string to decimal in controller and have the control how to parse (which locale to use). This solution works but brings problems like code first databases (it is not good practice to use string instead of decimal) and parsing in controllers which brings additional code. Once again a simple solution is custom binder. Here is example code of my binder:

public class DecimalModelBinder : IModelBinder 
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (string.IsNullOrEmpty(valueResult.AttemptedValue))
            return null;

        var modelState = new ModelState {Value = valueResult};
        object actualValue = null;
        try
        {
            actualValue = decimal.Parse(valueResult.AttemptedValue, NumberStyles.AllowDecimalPoint,
                CultureInfo.CurrentCulture);
        }
        catch (FormatException e) {
            modelState.Errors.Add("Vnos ni pravilne oblike.");
        }

        bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
        return actualValue;
    }
}

I made this binder universal binder for decimal and decimal? in my mvc project. To achieve this simply add this two lines of code to Global.asax:

ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());

There is another use of decimal binder. If you want to localize default string which is displayed at parse error you would need to override a specific localization file and put it into App_GlobalResources. Usually in ASP.NET MVC project resource files are not put in that folder because they cannot be used across app. So this means that you would have to make only one localization string in one file only to override default parse error message. Using this DecimalModelBinder in example before gives you option to localize like all other strings.

And the last case where custom binders helped me are derived classes. Sometimes it might happen you have a base class (which is abstract) and derived classes. In your view you want to use base class and display different derived classes. Here is where custom binder really comes into action. If you wouldn't create them by default .NET would try to create an instance of base class. That would run your application to go into error. All you have to do is create custom binder for base model. NOTE: You have to put the name of you derived class in base model or in route. With this name you can create instance of that derived class. The trick behind it is to create actual instance of derived class which is necessary to display view.

Here is an example of my base class binder:

public class BaseClassBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var type = controllerContext.HttpContext.Request.QueryString["derivedClass"];//gets the type (convert name of ype to you type)
        var model = (DerivedClass) Activator.CreateInstance(type); // creates actual instance
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());

        return model;
    }
}

And to register it add the following line to Global.asax.

ModelBinders.Binders.Add(typeof(BaseClass), new BaseClassBinder());

This are three examples where are some practical usages shown of how to use custom binders. Note that there are several other methods which can be overridden in IModelBinder interface which you can use for. For additional help look at the documentation.