Convention over Configuration with MVC and Autofac
One of the key things to wrap your head around when doing good software development using frameworks like ASP.NET MVC is the idea of convention over configuration (or coding by convention).
The idea is that rather than messing around with configuration files about where to find things, how to register IoC containers, etc. that we used to do, you follow a convention, a way of doing things. The example normally expressed is a class in your model called “Sale” and a database named “Sales”. If you don’t do anything that’s how the system will work but if you decide to change the name of the database to “Products_Sold” then you need some kind of configuration to tell the system how to find the backend database. Otherwise it can naturally find it based on the naming strategy of your domain.
MVC does this by default. When you create a controller named “Home” the class is HomeController and the views of the HomeController are found in /Views/Home for your project. When dealing with IoC containers there’s the task of registering your types to resolve correct. So let’s take a step back and take a look at a project with several repositories in it (a repository here being some kind of abstraction over your data store).
Here we have under our Models: Customer, Invoice, and Product with their respective classes, repositories, and interfaces:
This might be how your project is typically setup. I want to inject the dependencies on my repositories into my controller (via my IoC system) like this:
1: public class HomeController : Controller
2: {
3: private readonly ICustomerRepository _customerRepository;
4: private readonly IInvoiceRepository _invoiceRepository;
5: private readonly IProductRepository _productRepository;
6:
7: public HomeController(
8: ICustomerRepository customerRepository,
9: IInvoiceRepository invoiceRepository,
10: IProductRepository productRepository)
11: {
12: _customerRepository = customerRepository;
13: _invoiceRepository = invoiceRepository;
14: _productRepository = productRepository;
15: }
16:
17: //
18: // GET: /Home/
19: public ActionResult Index()
20: {
21: return View();
22: }
23: }
Then somewhere in my controller I’ll use the various repositories to fetch information and present it to the user (or write back values gathered from the user). How do my IoC know how to resolve an ICustomerRepository object?
Here’s how I have my IoC engine setup for this sample using Autofac. You can use any IoC engine you want but I find Autofac works well with MVC. First in Global.asax.cs I just follow the same pattern that the default projects setup and add a new static class called ContainerConfig.RegisterContainer()
1: public class MvcApplication : System.Web.HttpApplication
2: {
3: protected void Application_Start()
4: {
5: AreaRegistration.RegisterAllAreas();
6:
7: WebApiConfig.Register(GlobalConfiguration.Configuration);
8: FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
9: RouteConfig.RegisterRoutes(RouteTable.Routes);
10: BundleConfig.RegisterBundles(BundleTable.Bundles);
11: ContainerConfig.RegisterContainer();
12: }
13: }
Next is setting up Autofac. First add the Autofac MVC4 Integration package either through the NuGet UI or the Package Manager Console:
PM> Install-Package Autofac.Mvc4
Next here’s my new ContainerConfig class I created which will register all the types I need:
1: public class ContainerConfig
2: {
3: public static void RegisterContainer()
4: {
5: var builder = new ContainerBuilder();
6: builder.RegisterControllers(Assembly.GetExecutingAssembly());
7: builder.RegisterType<CustomerRepository>().As<ICustomerRepository>();
8: builder.RegisterType<InvoiceRepository>().As<IInvoiceRepository>();
9: builder.RegisterType<ProductRepository>().As<IProductRepository>();
10: var container = builder.Build();
11: DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
12: }
13:
14: }
What’s going on here:
Line 5 | Create a new ContainerBuilder |
Line 6 | Register all the controllers using the assembly object |
Line 7-9 | Register each repository |
Line 10 | Build the container |
Line 11 | Set the default resolver to use Autofac |
Pretty straight forward but here are the issues with this approach:
- I have to keep going back to my ContainerConfig class adding new repositories as the system evolves. This means not only do I have to add the classes/interfaces to my system, I also have to remember to do this configuration step. New developers on the project might not remember this and the system will blow up when it can’t figure out how to resolve INewRepository
- I have to pull in a new namespace (assuming I follow the practice of namespace = folder structure) into the ContainerConfig class and whatever controller I add the new repository to.
- Repositories are scattered all over my solution (and in a big solution this can get a little ugly)
A little messy. We can do better with convention over configuration.
First step is to move all of your repositories into a new folder called Repositories. With ReSharper you can just use F6 to do this and it’ll automatically move and fix the namespaces for you, otherwise use whatever add-in your want or move it manually. This includes both the classes and interfaces. Here’s what our solution looks like after the move:
Pretty simple here but how does this affect our code? Really minimal. In the controller(s) that you’re injecting the repository into, you just have to remove all the old namespaces and replace it with one (whatever namespace your repositories live in).
The other change is how you register your ContainerConfig. Here’s the updated version:
1: public class ContainerConfig
2: {
3: public static void RegisterContainer()
4: {
5: var builder = new ContainerBuilder();
6: builder.RegisterControllers(Assembly.GetExecutingAssembly());
7: builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
8: .Where(x => x.Namespace.EndsWith(".Repositories"))
9: .AsImplementedInterfaces();
10: var container = builder.Build();
11: DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
12: }
13: }
Note that a) we only have one line to register all the repositories now and b) the namespace dependency we had in our file is now gone.
The call to RegisterAssemblyTypes above using the convention of looking for any class/interface in a namespace that ends with “.Repositories” and then simply registers them all.
So for the new developer on the project the instructions to them are to just create new repository classes and interfaces in the Repositories folder. That’s it. No configuration, no mess.
Hope that helps!