August 10, 2011

Model Binder Dependency Injection with Structuremap

Using a custom ModelBinder in ASP.NET MVC can really help clean up your controllers. For example, in my most recent project we needed access to a “Blog” object within every action method on our controller. Originally we were calling the following code in our controller’s OnActionExecuting event:

if (siteContext != null) {
	blog = blogService.GetBlog(siteContext.SiteId);
}

We were able to swap this out with a custom model binder like so:

public class BlogModelBinder : IModelBinder
{       
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var siteContext = DependencyResolver.Current.GetService<ISiteContext>();
		var blogService = DependencyResolver.Current.GetService<IBlogService>();
		
		if (!siteContext.Request.IsAuthenticated)
        {
            return null;
        }

        return blogService.GetDefaultBlog(siteContext.Site.Id);
    }
}

Then we would just change our Action methods to take a Blog as a parameter:

[HttpGet]
public ActionResult Settings(Blog blog) {
	// Do stuff
}

And of course, not forgetting to register the custom ModelBinder when our application starts:

protected void Application_Start()
{
	// Other initialization code
	
	ModelBinders.Binders.Add(typeof(Blog), new BlogModelBinder());
}

Unfortunately you can already see a few “smells” in our code. The first is that we are calling our IoC container directly (to me calling the DependencyResolver is just as bad) within our ModelBinder code. The second is that we are effectively registering our ModelBinder as Singleton so even if constructor injection were possible OOTB, we would face problems when we needed to access contextual data.

The solution to this is to implement our own IModelBinderProvider.As per the MSDN docs, IModelBinderProvider

Defines methods that enable dynamic implementations of model binding for classes that implement the IModelBinder interface.

We can effectively use our custom provider to return an IModelBinder instance for a given type. If we can’t create the binder, we just return null which will fall back to using default model binder.

Here’s our StructureMapModelBinderProvider:

public class StructureMapModelBinderProvider : IModelBinderProvider
{
    private readonly IContainer container;

    public StructureMapModelBinderProvider(IContainer container) {
        this.container = container;
    }
    
    public IModelBinder GetBinder(Type modelType) {
        var mappings = container.GetInstance<ModelBinderMappingDictionary>();
        if (mappings != null && mappings.ContainsKey(modelType))
            return container.GetInstance(mappings[modelType]) as IModelBinder;

        return null;
    }
}

The first thing that may look strange is the ModelBinderMappingDictionary. This is just a simple dictionary we have created to store our Model Binder mappings:

public class ModelBinderMappingDictionary : Dictionary<Type, Type>
{
    public void Add<TKey, TModelBinder>() where TKey : class where TModelBinder : IModelBinder {
        this.Add(typeof(TKey), typeof(TModelBinder));
    }
}

Next we need to register our mappings and we need to tell the ASP.NET MVC framework to use our custom model binder provider. Since we’re already using a StructureMap implementation of IDependencyResolver this is just a case of registering our custom implementations in the normal fashion, in our StructureMap registry:

var binders = new ModelBinderMappingDictionary();
binders.Add<Blog, BlogModelBinder>();

For<ModelBinderMappingDictionary>().Use(binders);

For<IModelBinderProvider>().Use<StructureMapModelBinderProvider>();

Now we can change our BlogModelBinder to use constructor injection:

public class BlogModelBinder : IModelBinder
{
    private readonly ISiteContext siteContext;
    private readonly IBlogService blogService;

    public BlogModelBinder(ISiteContext siteContext, IBlogService blogService)
    {
        this.siteContext = siteContext;
        this.blogService = blogService;
    }
    
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (!siteContext.Request.IsAuthenticated)
        {
            return null;
        }

        return blogService.GetDefaultBlog(siteContext.Site.Id);
    }
}

So there you have it, dependency injection in a custom ASP.NET MVC Model Binder, using StructureMap.

© 2022 Ben Foster