Friday, August 12, 2011

Register ViewModels with Autofac by overriding DefaultModelBinder

Autofac is a great IoC framework and we all know that. I however had a minor problem when registering ViewModels for my ASP.Net MVC application. If you register your viewmodels like a regular assembly or regular types, it will work perfect only if your viewmodel does not have any dependency or it has a parameter less constructor.

Here’s the example:

public class CustomerController : Controller
{
public ActionResult Index(CustomerViewModel viewModel)
{
return View(viewModel);
}


}


public class CustomerViewModel
{
ICustomerRepository repository;
public CustomerViewModel(ICustomerRepository repository)
{
this.repository = repository;
GetAllCustomers();
}

private void GetAllCustomers()
{
//Do something
}
}




if you’ve registered CustomerViewModel as following then this will throw a runtime error.



builder.RegisterType<CustomerViewModel>();



 



This does not work because Autofac will not intercept when CustomerViewModel will be created by DefaultModelBinder.



Here’s a workaround:



Create a new CustomeModelBinder class which will inherit from DefaultModelBinder and override the CreateModel method. Using DependencyResolver class, Autofac will now intercept and resolves dependency of viewmodels.



public class CustomModelBinder<T> : DefaultModelBinder where T : class
{
protected override object CreateModel(ControllerContext controllerContext,
ModelBindingContext bindingContext, Type modelType)
{
var item = DependencyResolver.Current.GetService<T>();

if (item != null)
{
return item;
}
throw new ArgumentException(string.Format("model type {0} is not registered", modelType.Name));
}
}


 



Now a simple extension method to Register all viewmodels (assuming the convention is have all viewmodel class name ends with “ViewModel”)



 



public static void RegisterViewModels(this ContainerBuilder builder)
{
var types = System.Reflection.Assembly.GetExecutingAssembly().GetTypes().Where(x => x.Name.EndsWith("ViewModel", StringComparison.OrdinalIgnoreCase));
types.ToList().ForEach(modelType =>
{
Type modelBinderType = typeof(CustomModelBinder<>);
Type[] typeArgs = { modelType };
Type genType = modelBinderType.MakeGenericType(typeArgs);
object viewModel = Activator.CreateInstance(genType);
ModelBinders.Binders.Add(modelType, (IModelBinder)viewModel);
builder.RegisterType(modelType).InstancePerDependency();
});
}


Alternatively, if all your viewmodels inherit from a common base class (say, BaseViewModel) then following extension can be handy.



public static void RegisterBaseViewModelDescendants<T>(this ContainerBuilder builder) where T : BaseViewModel
{
var types = System.Reflection.Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsSubclassOf(typeof(T)));
types.ToList().ForEach(modelType =>
{
Type modelBinderType= typeof(IocModelBinder<>);
Type[] typeArgs = { modelType };
Type genType = modelBinderType.MakeGenericType(typeArgs);
object viewmodel = Activator.CreateInstance(genType);
ModelBinders.Binders.Add(modelType, (IModelBinder)viewmodel);
builder.RegisterType(modelType).InstancePerDependency();
});
}