Ivan's Space

Writing about leadership, management, emotional resiliency, software engineering, tech, gadgets.




read

There are a few blog posts around the web regarding ASP.NET MVC custom model binding, but there are two key pieces of information missing.

Just a quick intro – to implement custom model binding in ASP.NET MVC we need to implement the IModelBinder interface:

public interface IModelBinder
{
    object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
}

For the sake of the example let’s say we have a class (below) that doesn’t have a public constructor and for one or another reason we don’t want to have a view model.

public class Quantity
{
    public Quantity (float value, MeasurementUnits units)
    {
          this.Value = value;
          this.Units = units;
    }

    public float Value { get; set; }
    public MeasurementUnits Units { get; set; }
}

public enum MeasurementUnits  { mg, g, kg, l, kcal }

With this class in ASP.NET MVC the model binding won’t work, because it doesn’t expose a public constructor. We have three options:

  1. “Mirror” the Quantity class in a View Model class, but that also requires mirroring each class that has a property of type *Quantity * as well
  2. Manually build an instance by retrieving the units and value from the Request parameters. However this will have to be done in each controller action (code duplication is bad).
  3. Use custom model binding.

Here is the model binder:

public class QuantityModelBinder : IModelBinder
{
    public object BindModel (ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (controllerContext == null)
            throw new ArgumentNullException ("controllerContext", "controllerContext is null.");
        if (bindingContext == null)
            throw new ArgumentNullException ("bindingContext", "bindingContext is null.");

        MeasurementUnits? units = TryGet<MeasurementUnits> (bindingContext, "Units");
        float? value = TryGet<float> (bindingContext, "Value");

        if (units.HasValue && value.HasValue)
            return new Quantity (value.Value, units.Value);

        return null;
    }

    private Nullable<T> TryGet<T> (ModelBindingContext bindingContext, string key) where T : struct
    {
        if (String.IsNullOrEmpty (key))
            return null;

        ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "." + key);
        if (valueResult == null && bindingContext.FallbackToEmptyPrefix == true)
            valueResult = bindingContext.ValueProvider.GetValue (key);

        bindingContext.ModelState.SetModelValue (bindingContext.ModelName, valueResult);

        if (valueResult == null)
            return null;

        try {
            return (Nullable<T>)valueResult.ConvertTo (typeof (T));
        } catch (Exception ex) {
            bindingContext.ModelState.AddModelError (bindingContext.ModelName, ex);
            return null;
        }
    }
}

The two important gotchas are:

  1. If your binding validation fails you should log an appropriate validation error via *ModelState.AddModelError . *It is useful to know that if you add exceptions of type FormatException to the model errors, ASP.NET MVC is smart enough to automatically convert them to string errors post-binding but before it returns control to your controller action.
  2. If  you log an error you *must **call *ModelState.SetModelValue or otherwise if you return null it will cause a NullReferenceException in the default ASP.NET MVC binder. This is so because unless you call that method the rest of the ASP.NET MVC code (as well as your own code) won’t be able to access ModelState[propertyName] and because it doesn’t handle that it blows up.

P.S:  If I have to be pedantic and the code path is not critical I won’t use strings in the model binder to refer to the Quantity properties. Instead I would use something like what I’ve written about in will use the method described in How to avoid passing property names as strings using C# 3.0 Expression Trees .

Blog Logo

Ivan Zlatev


Published

Image

Ivan's Space

Writing about leadership, management, emotional resiliency, software engineering, tech, gadgets.

Back to Overview