OdeToCode IC Logo

Delegated Model Binding

Sunday, July 18, 2010

In the last post we saw the recursive nature of the default model binder in MVC. Now, let's look at the following class:

public class User
{
    public string Name { get; set; }
    public string Email { get; set; }
    
    /* 
     * bunch more simple stuff 
     */

    public AuthorizationClaims AdditionalClaims { get; set; }
}

Imagine AdditionalClaims are something that the default model binder will not understand. For whatever reason, you'll need to use a custom model binder to pull data from the request environment and into the claims property. If you don't know how the default model binder works, you might think "because the model binder doesn't understand part of this model, I'll have to write a model binder for the whole model", and do this:

public class UserModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext,
                            ModelBindingContext bindingContext)
    {
        // stuff ...
    }
}

And register the class during application startup:

ModelBinders.Binders.Add(typeof (User), new UserModelBinder());

This probably isn't the approach you want to use for a couple reasons:

1) The model binder is not only responsible for claims, but also for all the other properties (name, email, etc).

2) You've only solved the problem of binding claims in a single model type. That's fine if AuthorizationClaims only appears in a single model type, but maybe it doesn't (and maybe it won't in the future).

Remember the default model binder delegates the work of binding each property. Knowing how the default model binder works, it's easier to focus in on just the piece you need to customize.

public class AuthorizationClaimsModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext,
                            ModelBindingContext bindingContext)
    {
        /* implementation details */
    }
}

And:

ModelBinders.Binders.Add(typeof(AuthorizationClaims),
                new AuthorizationClaimsModelBinder());

Now the claims binding will work anywhere - even when claims are nested in another type, and it doesn't require code changes in the future.

However ...

Isn't there always a catch?

In order for the default model binder to delegate the work of creating an AuthorizationClaims it first has to think there is an AuthorizationClaims available. It does this using "value providers" and "prefixes". We'll talk about these concepts and how to implement the model binder in a future post (but not right away - too much model binding at once is boor - riiiing).