Skip to content

John Bennett's blog

Removing dependencies between controllers and Linq DataContext classes with dependency injection

Saturday, December 22, 2007

In ScottGu’s most recent post on ASP.NET MVC, he demonstrates using a partial class for the NorthwindDataContext created by the Linq to Sql designer.  In the partial class he creates some simple wrapper methods for the ProductsController to use when retrieving and persisting data.  However, the ProductsController is still tightly coupled to NorthwindDataContext.  Let’s decouple them using an interface and some dependency injection…

If you want to follow along, you’ll need to download ScottGu’s sample, which I’ll use as my starting point.  The sample (MvcApplication5) uses the MvcToolkit, but I found I had to download MvcToolkit and repair the reference to before I could successfully build and run the sample.

Creating the IProductsRepository interface

The first step is to create an IProductsRepository interface.  We’ll use VS2008′s refactoring support to extract an interface from Scott’s NorthwindDataContext partial class.  Open that file in the editor, right-click on the partial class declaration and choose Refactor > Extract Interface.


Name the interface IProductsRepository and select just the five methods that Scott added in the partial class.

Select methods to include in interface

That will create IProductsRepository.cs in the Models folder with the new interface.

The new IProductsRepository interface

It also adds the interface to the NorthwindDataContext declaration.  Now we can go to the ProductsController in the Controllers directory and change the northwind field to be of type IProductsRepository.

Change private field to interface type

At this point, we have a problem.  Both the Create() and Update() action methods call SubmitChanges() on the data context.  When we try to build, the compiler tells us that the SubmitChanges method isn’t part of the IProductsRepository interface.  The whole goal here is to reduce the coupling between the controller and the Linq-generated DataContext.  So we’re going to move the Linq code into the partial class and access it through the interface we just created.

Let’s start with Create().  Create() calls AddProduct() on the data context, which looks like this:

AddProduct() before

Let’s add a call to SubmitChanges() to AddProduct():

AddProduct() after

We have to remove SubmitChanges() from Create():

Create() after

There is no method in our IProductsRepository interface that the controller can call when updating a product, so we’ll create a SaveProduct() method in the interface and implement it in NorthwindDataContext.  The only thing our implementation of SaveProduct() will do is call SubmitChanges().  So Update() looks like this:

Update() after

And SaveProduct() like this:

SaveProduct() method

Setting up for dependency injection with Windsor

At this point, everything works as before, we’ve just added an interface and moved some calls to SubmitChanges().  ProductsController and NorthwindDataContext are still tightly coupled.  For the next part, we’re going to use dependency injection and our new interface to break the coupling.  For some background on dependency injection and why you might want to use it, see this article and links by James Shore.

There are a number of tools that will do dependency injection for you.  We’re going to use Windsor from the Castle Project.  We’ll also use MvcContrib, a community-driven project on CodePlex that is developing useful add-ons to ASP.NET MVC.  One of those add-ons is a WindsorController, which allows us to easily use dependency injection on our controllers.

You’ll also need to download the latest release of the MvcContrib project.  I’m using the beta version in this sample.  We need to add a couple of references to the MvcToolkit assemblies.  When you download the binaries version, you’ll have a bin directory with the assemblies and a Samples directory with some code that we’ll use below.

Add references in MvcApplication5 to:

  • Castle.Core.dll
  • Castle.MicroKernel.dll
  • Castle.Windsor.dll
  • MvcContrib.dll
  • MvcContrib.Castle.dll.

Now we need to modify our Global.asax.cs file so that our web application automatically creates a Windsor Container.  All of these changes are below are also shown in the sample app provided in the MvcContrib download.

  • Add using statements for the Castle.Core and Castle.Windsor namespaces.
  • Implement the IContainerAccessor interface, which has a single property named Container.
  • Add a static field to hold the container, and a static property to wrap it.

Here’s what they look like so far:

Changes to

Next up is to instantiate the container and make it aware of all the controller classes in our application.  This is done by looking for all types in the current assembly that can be cast to the IController interface.

  • Add a using statement for the MvcContrib.ControllerFactories namespace.
  • Copy in the InitializeWindsor() method from the sample app.
  • Refactor the route handling setup from Application_Start() to a separate method named AddRoutes().
  • Change the Application_Start() handler to call InitializeWindsor() and AddRoutes().

The end result in Global.asax.cs is:

InitializeWindsor() method

Two changes to the sample code are required.  The first change is to tell the WindsorContainer to use the web.config file to get the configuration values.  We’ll go into the config settings in the next section.  Here all we need to do is pass into the WindsorContainer constructor a new XmlInterpreter().  This tells the container to use the current app.config or web.config file.

The sample uses a controller class named WindsorController to get access to the assembly containing all the controllers.  Since we don’t have a WindsorController type, our second change is to:

  • Add a using statement for the MvcApplication5.Controllers namespace.
  • In the InitializeWindsor() method (line 42 in the screenshot) change “typeof(WindsorController)” to “typeof(HomeController)”.  I use HomeController, since most ASP.NET MVC applications will have one of those.

After these changes, the two lines in InitializeWindsor() look like this:

InitializeWindsor() changes

Injecting the repository into ProductsController

Getting back to our ProductsController class, remember that we changed the northwind field to be of type IProductsRepository, but we still populated it with an instance of NorthwindDataContext. What we want is for ProductsController to know nothing about NorthwindDataContext.  Any class that implements IProductsRepository should work just fine for retrieving and saving products.

First, we’ll remove the assignment of the NorthwindDataContext instance.  While doing this, we’ll change the field name from “northwind” to “repository” to make it more generic.  VS2008′s refactoring support makes this a breeze.  Then we’ll add a constructor that takes a parameter of type IProductsRepository.  (Make sure the IProductsRepository interface is declared as public, since we didn’t do that earlier.)  The result is:

Add constructor for DI

We’re now ready to tell Windsor: “Whenever you create a ProductsController, pass a NorthwindDataContext into its constructor.”  We do that through a config section in web.config.  First declare a section for castle in the <configSections> element and add a <castle> element as follows:

Configure Windsor

What we’re telling Windsor here is that whenever a controller has a constructor parameter of type IProductsRepository, give it a new instance of NorthwindDataContext.

At this point, we can run the application to see that it all works just like it did in Scott’s original version.  The difference now is that our controller and data context are no longer tightly coupled.  If we wanted to change from Linq to some other ORM tool, or to move the Products table to a different Linq data context class than the rest of Northwind, before we’d have to make a bunch of code changes.  Now all we have to do is update our web.config file to point Windsor at a class that implements IProductsRepository and we’re done.

Enjoy.  And please comment on what’s confusing, what I glossed over too quickly, and what I spent too much time explaining.  I’m still getting the hang of this and welcome any suggestions.