Intelligent model binding with model binder providers

So that better model binder I built a couple of years ago to address conditional model binding in ASP.NET MVC 1-2 is obsolete with the release of ASP.NET MVC 3. Instead, the concept of a model binder provider allows this same functionality, fairly easily. Back in my Put Your Controllers on a Diet talk at MVCConf, I showed how we can get rid of all those pesky “GetEntityById” calls out of our controller actions. We wanted to turn this:

public ActionResult Show(Guid id)
{
    var conf = _repository.GetById(id);

    return AutoMapView<ConferenceShowModel>(View(conf));
}

Into this:

public ActionResult Show(Conference conf)
{
    return AutoMapView<ConferenceShowModel>(View(conf));
}

We can use a custom model binder to achieve this result. However, our original implementation used model binders per concrete entity type, not too efficient:

ModelBinders.Binders
    .Add(typeof(Conference), new ConferenceModelBinder());

The problem here is that we’d have to add a model binder for each concrete entity type. In the old solution from my post for a better model binder, we solved this problem with a model binder that also included a condition on whether or not the model binder applies:

public interface IFilteredModelBinder
    : IModelBinder
{
    bool IsMatch(ModelBindingContext bindingContext);
}

However, this is exactly what model binder providers can do for us. Let’s ditch the filtered model binder and go for the model binder provider route instead.

Custom model binder provider

Before we get to the model binder provider, let’s first build out a generic model binder. We want this model binder to not just accept a single concrete Entity type, but any Entity type we supply:

public class EntityModelBinder<TEntity> 
    : IModelBinder
    where TEntity : Entity
{
    private readonly IRepository<TEntity> _repository;

    public EntityModelBinder(IRepository<TEntity> repository)
    {
        _repository = repository;
    }

    public object BindModel(ControllerContext controllerContext, 
        ModelBindingContext bindingContext)
    {
        ValueProviderResult value = bindingContext
            .ValueProvider.GetValue(bindingContext.ModelName);

        var id = Guid.Parse(value.AttemptedValue);

        var entity = _repository.GetById(id);

        return entity;
    }
}

We took the model binder used from the original “controllers on a diet” talk, and extended it to be able to handle any kind of entity. In the example above, all entities derive from a common base class, “Entity”. Our entity repository implementation (although it could be any common data access gateway) allows us to retrieve the specific kind of entity (Customer, Order, Conference, whatever) by filling in the type. It’s just another example of using type information as a means of altering behavior in our system.

In our previous incarnation, we would create either our IFilteredModelBinder to be able to handle any base entity type. If we didn’t have that kind of abstraction in place, we’d have to register concrete implementations for each concrete entity type. Not ideal. Instead, let’s build a model binder provider:

public class EntityModelBinderProvider
    : IModelBinderProvider
{
    public IModelBinder GetBinder(Type modelType)
    {
        if (!typeof(Entity).IsAssignableFrom(modelType))
            return null;

        Type modelBinderType = typeof(EntityModelBinder<>)
            .MakeGenericType(modelType);

        var modelBinder = ObjectFactory.GetInstance(modelBinderType);

        return (IModelBinder) modelBinder;
    }
}

First, we create a class implementing IModelBinderProvider. This interface has one member, “GetBinder”, and the parameter is the type of the model attempting to be bound.  Model binder providers return a model binder instance if it’s able to bind based on the model type, and null otherwise. That allows the IModelBinderProviders to have the same function in our IFilteredModelBinder, but just in a slightly modified form.

If it does match our condition, namely that the model type is derived from Entity, we can then use some generics magic to build up the closed generic type of the EntityModelBinder, build it from our IoC container of choice, and return that as our model binder.

Finally, we need to actually register the custom model binder provider:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    ModelBinderProviders.BinderProviders
        .Add(new EntityModelBinderProvider());

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

We now have just one model binder provider that can handle any bound entity in our incoming model in our controller actions. Whereas MVC 1-2 forced us to either come up with a new abstraction or register specific types, the IModelBinderProvider allows us to make intelligent decisions on what to bind, without incurring a lot of duplication costs.

Yet another set of code to delete when moving to ASP.NET MVC 3!

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Ryan Anderson

    Sweet! I thought you said you haven’t done any web stuff in forever?

    • Anonymous

      Ha well…that WAS true…

  • http://twitter.com/hazzik Alexander I. Zaytsev
  • Vladan

    Can you make an example where you also load children? -One and -Many relations? And then update them with contents of form?

  • Anonymous

    bindingContext.ValueProvider.GetValue(bindingContext.ModelName) will return null, because in request exist only id (as guid), so you need get value like bindingContext.ValueProvider.GetValue(“id”)

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #889

  • James Morcom

    Out of interest – how do you ensure that the current user has permission to load the entity with the given ID? Or do you only use this when all users have access to all entities of that type?

    Thinking in terms of this security guideline https://www.owasp.org/index.php/Top_10_2010-A4

    • Anonymous

      Mmmm, we would just go for an action filter that required a permission to view the entity. Decorate the action, and it prevented access. Is that what you’re talking about?

      • James Morcom

        Thanks for responding :)

        I think so – for example if the Show action was showing an order, and you want to prevent the user from being able to see other peoples’ orders by fiddling with the query string.

        In the original example you’d probably have a repository call something like:

        orderRepository.GetOrdersFor(CurrentUser).Single(o => o.ID == id);

        or maybe replace the repository call with

        CurrentUser.Orders.Single(o => o.ID == id);

        which seems difficult to do when the load call is encapsulated in the model binder?

        • Anonymous

          Ah, in that case, the original code in my controller action doesn’t look like the code I’m trying to replace. I’d only shoot for those places that all look the same.

          We’ll still often go this route, but have our action filter be able to examine the model being passed in (which we have access to). It’s still possible, it just depends what feels like encapsulation versus burying.

          • James Morcom

            I’m with you – this removal of duplication works great for something like an administration interface that’s essentially a collection of CRUD forms for different objects. In this case the user isn’t restricted to which entities they can work with.

  • Hightowr

    Great stuff :)
    What about URL generation? Won’t that be a problem now?

  • http://twitter.com/ntcoding Nick

    Doesn’t this cause a problem with the strongly-typed action links aswell? The action link is looking for an item of T:Entity, but you only want to pass the id?

    • Anonymous

      Yeah, it makes those a little weird to deal with. But the version of action links we had didn’t evaluate parameters, so we went a different direction anyway.

  • http://www.grumpydev.com/ Steven Robbins

    MakeGenericType is pretty slow so you may want to cache the types you create (MVC doesn’t seem to cache the results of GetBinder from what I can see) – in .net4 something simple like this should do the trick:

    public class EntityModelBinderProvider : IModelBinderProvider{    private readonly ConcurrentDictionary _cache = new ConcurrentDictionary();    public IModelBinder GetBinder(Type modelType)    {        if (!typeof(Entity).IsAssignableFrom(modelType))            return null;        var modelBinderType = _cache.GetOrAdd(            modelType, t => typeof(EntityModelBinder).MakeGenericType(modelType));        var modelBinder = Activator.CreateInstance(modelBinderType);        return (IModelBinder)modelBinder;    }}

    • Anonymous

      Ah cool, thanks!

  • http://twitter.com/ardalis Steve Smith

    My original code looked something like this:
            public ActionResult Create(int publicationId)
            {
                var publication = _publicationRepository.Get(publicationId);
            }
    However, in the BindModel method, ValueProvider.GetValue(bindingContext.ModelName) was looking up “publication” and getting null.  I changed the line like so and it works for me:
                ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + “Id”);

    I’m also using integer IDs, so my full BindModel became:

            public object BindModel(ControllerContext controllerContext,
                ModelBindingContext bindingContext)
            {
                ValueProviderResult value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + “Id”);

                var id = Int32.Parse(value.AttemptedValue);

                var entity = _repository.Get(id);

                return entity;
            }

    • Anonymous

      Ha, that’s weird. Wonder why the Id part got cut off…

  • dario-g

    Or maybe something like this (one line, too):

    return View(AutoMapView(id));Then we can return View or Json or something else.Inside AutoMapView we can check permission to view conference details by loggeduser, check for existing row in database otherwise “page not found” or something likethat, etc….. and of course map Entity to ViewModel. :) What do You think?

    • James Morcom

      This is similar to what I envisaged – keeping the ability to restrict the entities the user has access to rather than hiding it in the model binder. When I see this though I can’t help feeling that it’s just become an obsession on reducing lines of code, and I’m not sure that it isn’t trying to solve a problem that was never there in the first place :)

      Could you not argue that when it’s two lines, the first (repository/load) line shows you exactly what you’re loading from, and makes security concerns more explicit, and keeps the reponsibility of loading entities separate?

      • Anonymous

        More an obsession to eliminate duplication. Lots of times cross-cutting concerns are dealt with this way, where it looks like one method doesn’t get much better, but when I have lots and lots of controller actions that look the same, it allows us to define a common execution path for these guys.

        It’s still one of those situations where I’m trying to eliminate duplication that *already exists*, and not put something in that looks cool before I’ve established patterns of usage in my controllers.

    • Anonymous

      Looks like Disqus hates generics!

    • Anonymous

      Looks like Disqus hates generics!

  • http://twitter.com/arnislapsa Arnis Lapsa

    Jimmy, in case You use ORM like NHibernate, how do You handle eager loading?
    I mean – those model binders are kind a context-less.

  • Kekerain2011
  • http://twitter.com/mattRo55 Matt Ross

    How do you handle dependency injection in custom model binders?
    I’m reading mixed opinions about accessing data in model binders. Specifically relating to IoC.

    Is it possible, and if so, is it right?

    • Anonymous

      You can go a couple of routes – but in this case, you’re likely constrained to poor man’s DI + service location, unfortunately.

  • http://twitter.com/chester89 Gleb Chermennov

    thanks for the post! btw, is it possible to write an analog for adding and updating entities?

  • http://profiles.google.com/smckoy Sam McKoy

    Neat idea, although I’d love to know how you handle whether to load child/navigation properties.  I’d rather not turn on lazy loading.  Maybe I need a concrete repository per entity type =(.

  • Hemslingo

    Really nice idea! Do you use reflection in your respository to eager load child entities?

    • Anonymous

      No, we haven’t needed to pull child entities.

  • Hemslingo

    How are you implementing ObjectFactory?

  • Hemslingo

    Im using Ninject as my IOC container and I just cannot figure how to call a GetInstance() method without duplicating kernals as the kernal is instanciated in the Global.ascx already?

    I’m a big fan of this method and will be such a timesaver when I sort that issue, but thats really got me stumped at the mo ;-(

  • Hemslingo

    Ok for anyone using Ninject with ASP.NET MVC 3, I found a fix if you’re stuck on resolving the modelBinder from the modelBinderType. There is probably a better way of organising this within your application but essentially this will do.

    You can resolve this as so…

    System.Web.Mvc.DependencyResolver.Current.GetService(modelBinderType);

    As you dependancy resolver is registered on Application_Start() this should work with any IOC container not just Ninject.

    I hope this helps someone as Its sucked an hour or so from my soul!

  • Hemslingo

    Do you have Lazy Loading switched on in this application?

    • Anonymous

      I do – but entities in drop-downs don’t dig in to child entities.

  • Pingback: ASP.NET MVC: Using a custom model binder to post a list of interface objects | (Sitecore) Martina

  • asatp

    I have two questions, please let me know

    1. We are using NHibernate as our ORM software and I couldn’t find OBjectFactory reference with ASP.net mvc 4.0 and .net framework 4.5.1, what package I should install

    2. How to connect EntityModelBinder to DateTime custom model binder or how to include to convert a datetime filed that submited with entity type. (Entity type: user, and field: Expiration date, wants to convert it from EU to US culture)

    Thanks

    • jbogard

      ObjectFactory is from StructureMap. On the second piece…hmmm that’s a bit tougher. You’re binding from ID -> Entity, but the date comes from Database -> Entity, so you’ll likely have to hook in there.

      Unless the date is a field on the screen that you’re trying to *display*. Model binding only gets applied from Request -> Action, not Action -> Model -> View. You’re probably looking for something in the View layer for display purposes?