재우니의 블로그

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:

image

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()


Application_Start 발생시 이를 컨테이너에 등록한다.

   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:


Autofac.Mvc4 을 nuget 통해 다운로드 받는다.

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 5Create a new ContainerBuilder
Line 6Register all the controllers using the assembly object
Line 7-9Register each repository
Line 10Build the container
Line 11Set the default resolver to use Autofac

Pretty straight forward but here are the issues with this approach:

  1. 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
  2. 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.
  3. 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:

image

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:

Repositories 폴더에 interface 와 클래스를 쌍으로 맞춰서 구현한다. 네임스페이스 이름으로 찾아서 특정 네임스페이스 지정한 부분만 등록을 한다.

   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!


https://weblogs.asp.net/bsimser/convention-over-configuration-with-mvc-and-autofac