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.
That will create IProductsRepository.cs in the Models folder with the new 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.
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:
Let’s add a call to SubmitChanges() to AddProduct():
We have to remove SubmitChanges() from Create():
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:
And SaveProduct() like this:
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 0.0.1.32 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:
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:
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:
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:
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:
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:
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.