My last post was about how to let your IOC container manage ASP.NET MVC action filters. This time, let’s look at how to do the same with model binders.
Last month, Jimmy Bogard shared his SmartBinder implementation which solved this. For my requirements however, Jimmy’s implementation has one show-stopper: By injecting all the model binders into his custom binder resolver, he is essentially limiting the lifetime of binders to singleton behavior, since the resolver itself has to live as a singleton within ASP.NET MVC’s model binder dictionary. I think that a better solution would be to let the custom binder resolver forward binder resolution to the IOC container on an ad-hock basis. Here’s my GenericBinderResolver which does that:
public class GenericBinderResolver : DefaultModelBinder
{
private readonly ICanResolveDependencies _resolver;
private static readonly Type BinderType = typeof(ModelBinder<>);
public GenericBinderResolver(ICanResolveDependencies resolver)
{
_resolver = resolver;
}
public override object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
Type genericBinderType = BinderType.MakeGenericType(bindingContext.ModelType);
var binder = _resolver.Resolve(genericBinderType) as IModelBinder;
if (null != binder) return binder.BindModel(controllerContext, bindingContext);
return base.BindModel(controllerContext, bindingContext);
}
}
Notice how the binder resolver builds a generic ModelBinder<T> type from the model type, and asks the IOC container to resolve it (the container is abstracted by the ICanResolveDependencies interface). This way, the container is free to manage the lifetime of each model binder separately.
Binders are implemented by deriving from my generic ModelBinder<T> class:
public abstract class ModelBinder<T> : IModelBinder
{
protected abstract T BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext);
object IModelBinder.BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
return BindModel(controllerContext, bindingContext);
}
}
For example, here’s a model binder which gets an aggregate root from a domain service. This binder has to have a lifetime which is compatible with the per-request scope of the domain service it depends upon (note: I’ve simplified the binder a bit, removing validation etc).
public class PostModelBinder : ModelBinder<IPost>
{
private readonly IBlogService _blogService;
public PostModelBinder(IBlogService blogService)
{
_blogService = blogService;
}
protected override IPost BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var request = controllerContext.HttpContext.Request;
var postIdString = request["postId"];
IPost post = null;
if (!String.IsNullOrEmpty(postIdString))
{
var postId = Int32.Parse(postIdString);
post = _blogService.FindPost(postId);
}
return post;
}
}
Then, all I have to do is register the binder in my container (Ninject):
Bind<ModelBinder<IPost>>().To<PostModelBinder>().Using<RequestScopedBehavior>();