Dependency Injection in ASP.NET MVC - Part 2
Welcome to part two of "Dependency Injection in ASP.NET MVC". In part one, we discussed the basics of dependency injection (DI). Please refer to part one if you need a quick refresher on DI. Now that we have covered the basics of DI, let’s take a look at implementing DI in an ASP.NET MVC application. For the purposes of this article, I will be using ASP.NET MVC 3 and Ninject. In the application, we will create a set of data repositories and inject them into an ASP.NET MVC controller at run-time. This pattern will allow us to cleanly separate our data access logic from our presentation code, and set us up for unit testing. I will be using a simple college course catalog as our domain so we can focus our efforts on the DI implementation. There will be a single view with a simple bulleted list of course numbers and names. All of the data will be mock data defined in the code directly.
First, I am going to create a new ASP.NET MVC 3 web application in Visual Studio 2010. I selected the basic MVC 3 template and ended up with the solution below. I will not be using the Account features of the template, so I went ahead and deleted the Account controller and its associated View folder.
Now that we have our project in place, we need to pull Ninject into the project. The Nuget package manager makes this a very simple task. There is a Nuget package made specifically for Ninject integration into an ASP.NET MVC project. From the Nuget console, execute the following command to install the Ninject.MVC3 package.
What did that do for us? Notice that you will now find an App_Start folder in your project. Take a look at the NinjectWebCommon.cs file inside of the App_Start folder. This class is responsible for wiring up the Ninject kernel and ensuring that we can inject dependencies into our controllers automatically. The kernel is the component that actually handles creating instances of objects during the injection process. Take special note of the RegisterServices method. We will come back to this in a moment.
Now we need to create some additional items before we can complete the dependency injection configuration. We will need to define an interface for our course catalog repository called ICourseRepository. Additionally, we will define two implementations of ICourseRepository. Ultimately, we will be injecting an implementation of ICourseRepository into a new controller called CoursesController. You will notice that in both implementations I am using mock data. In a true implementation, we would have SQL Server or Oracle specific ADO.NET code. Alternatively, this would be a great place to plugin your favorite ORM such Entity Framework or NHibernate. For now, our focus will be on the SqlServerCourseRepository implementation. We will talk about the Oracle version at the end of this article.
Here is the repository code:
Notice that we have an interface defined and then two implementations called SqlServerCourseRepository and OracleCourseRepository. The interface features only a single method called LoadAll. The implementations have some basic hard coded data returning an IList. Course is a simple two-property class as defined here:
Next, we need to build a basic controller with a single Index action that returns an IList as the model. Notice that I have included a constructor in the controller that accepts an instance of ICourseRepository. As I mentioned in part one of this article, we are now set for constructor injection. In this case, our controller has a dependency on ICourseRepository and we are going to use Ninject to “inject” an instance into our controller via the constructor. The Index action is quite simple. We call the LoadAll method of the ICourseRepository instance and return the Index view.
Finally, we have some very simple view markup for displaying our courses in a basic un-ordered list.
There is one final configuration task that we need to complete. Jump back to the RegisterServices method in the NinjectWebCommon class in the App_Start folder. This is where we define or register our interfaces and what concrete implementations they map to. For now, let’s just use the SqlServerCourseRepository. The line below basically says, “If the Ninject kernel receives a request for an instance of ICourseRepository, then return an instance of SqlServerCourseRepository.”
If we run the application now and navigate to the Course Index, we will see this.
So what actually happened?
We configured our application to use Ninject via the Ninject.MVC3 Nuget package. This package handles nearly all of the Ninject wire-up to support dependency injection into our controllers via constructor injection. For the sake of this example, let’s just say that Ninject is now in charge of our controller factory. This was all part of the code that the Ninject.MVC3 package added to our project.
Via the RegisterServices method, we told Ninject to use an instance of SqlServerCourseRepository whenever an instance of ICourseRepository is required.
When we request the Index of the Courses controller, Ninject handles creating an instance of the controller. It inspects the constructor and sees that in order to create a controller instance; it must provide an instance of ICourseRepository. The Ninject controller factory checks the kernel for a binding for ICourseRepository, which it finds. The kernel spins up an instance of SqlServerCourseRepository, and uses it to create the controller instance.
Finally, we call the LoadAll method of our controller instance and return the data to the Index view.
Now that we have gone through all of this trouble to configure our application, you may be wondering why? By adhering to the principle of SoC or Separation of Concerns, we have decoupled our presentation code (Controllers and Views) from our data access code (SqlServerCourseRepository). Notice that in the controller, we only interact with an interface, ICourseRepository. The controller never actually knows what type of repository we are using since the instance is generated and “injected” and run-time.
Suppose our company is acquired by an Oracle-friendly organization. The new CIO decides that SQL Server is inferior to Oracle (yes, this has happened to me more than once) and we need to convert all of our applications to Oracle immediately. In our fictitious example above, we can do this with no impact to the presentation code since we are using a DI container combined with the Repository pattern. We would need to define new repository implementations that are Oracle aware, which we already did at the beginning of this article. Normally, you would not do that until it was actually required; but it works well for this example. Let’s visit the RegisterServices method again. Notice that we are now binding ICourseRepository to OracleCourseRepository.
And if we run the app again…
We are now serving up data from our mock Oracle repository without changing a single line of controller code! While this was a very simple example, it demonstrates the importance of SoC and just how easily SoC can be accomplished via dependency injection. Dependency Injection frameworks such as Ninject, facilitate SoC in your application architecture via a simple Nuget package and a handful of conventions.
In this article, we used DI to cleanly separate our data access code from our presentation code. This is only one application of dependency injection, albeit a common one. There are many other scenarios that lend themselves well to DI use, including dependency handling for MVC action filters. This is definitely a tool that should be in every developer’s toolbox.