Dependency Injection with MVC Framework (part 2)

In the first part of this we explored different strategies for using Entity Framework with the ASP .NET MVC Framework. We also discussed some of the weaknesses with existing models and how they do not meet our goals.  Now we will dive into using Dependency Injection as a means to get the Entity Framework data context instance to our business layer.  We want to maintain the ability to use one data context per request and make our model layer highly testable. We also want whatever method we decide upon to not create too many headaches for the developer and thus be easy enough to implement with a large amount of overhead; for this method we are taking the Dependency Injection route using Ninject.

This blog entry will not describe what Dependency Injection is, or how Ninject operates to accomplish it.  I will strive to arm you with the knowledge of what you must do to use Ninject with MVC as well as why we are doing what we are doing.  If you would like more information on Ninject, I suggest http://www.ninject.org.

To begin, what is necessary is that we intend to “inject” a single instance of our entity data context into a “service” layer.  This server layer is designed to hold our methods that will be referencing the entity data context.  If you think of it like this:

  • The View layer contains presentation information that is fed to it from the controller layer
  • The Controller layer dictates logical flow and is responsible for preparing the data before it is sent to the service layer. It is the traffic cop at the stop light
  • The Service layer, is implemented between the controller and model layer to act as a buffer and give us the ability to group related data methods.  An instance to each required service is created as a member for the controller. This allows us to list out a controllers dependencies very easily and effectively.
  • The model layer is responsible for creating an in-program representation of data from a data source and allowing us to work with data in a very object oriented fashion.

So lets see some sample code of what our controller could look like:

   1:  public class HomeController : Core.ControllerBase
   2:  {
   3:       [Inject]
   4:       public SeriesService SeriesService { get; set; }
   5:   
   6:       [Inject]
   7:       public StudioService StudioService { get; set; }
   8:   
   9:       public ActionResult Index()
  10:       {
  11:            ViewData["StudioList"] = StudioService.GetStudios();
  12:            return View(SeriesService.GetSeries());
  13:       }
  14:  }

So you will take note of the Service references, there is a StudioService and SeriesService.  Think of this as saying “the HomeController depends on access to both Studio and Series information”.  So this is what provides access to the series and studio information for the actions within the HomeController.

The key thing to take note here is the Inject attribute, which tells Ninject that this is a dependency insert an instance here when the parent object is created.  So when MVC instantiates the HomeController it will “inject” instances of SeriesService and StudioService into the HomeController instance.

All of this does NOT happen by magic however.  Out of the box, you will get a NullReferenceException with this code.  This is because, while we have marked our code appropriately we are not actually using Ninject because the HomeController instance is still being created by MVC and not by Ninject.

To get around this we need implement our own ControllerFactory that MVC will use which uses Ninject to create the instances of our controllers, so that it is aware of the Inject attribute and how to handle it.  Below is an implementation of the ControllerFactory using Ninject that I am using:

   1:  public class NinjectCustomControllerFactory : DefaultControllerFactory
   2:  {
   3:       protected override IController GetControllerInstance(Type controllerType)
   4:       {
   5:            IKernel kernel = new StandardKernel(new MVCModule());
   6:            return (IController)kernel.Get(controllerType);
   7:       }
   8:  }

Next we need to actually tell MVC Framework to use this new controller factory; we do this by updating our Global.asax Application_Start method, as such:

   1:  protected void Application_Start()
   2:  {
   3:       RegisterRoutes(RouteTable.Routes);
   4:       ControllerBuilder.Current.SetControllerFactory(typeof (Core.NinjectCustomControllerFactory));
   5:  }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

As I stated earlier, I am not going to explain how Ninject works or what this code does in depth.  It is sufficient to say that the Ninject kernel is used to create an instance of HomeController that is aware of the Inject attribute.  So what happens here is the HomeController (or any controller requested) is created through the Ninject kernel. Ninject will see the Inject attribute and add inject an instance of the dependencies into the class.

Now lets look at the SeriesService class:

   1:  public class SeriesService : INeedEntityManager
   2:  {
   3:       [Inject]
   4:       public AnimeManagerEntities EntityManager { get; set; }
   5:   
   6:       public Series GetSeries(int id)
   7:       {
   8:            return EntityManager.Series.FirstOrDefault(s => s.SeriesId == id);
   9:       }
  10:   
  11:       public List GetSeries()
  12:       {
  13:             return EntityManager.Series.ToList();
  14:       }
  15:  }

Notice the Inject attribute on the EntityManager, this is guaranteed by the INeedEntityManager interface.  I would normally put the Inject attribute in the interface, but it is here to save me from including more code.  Remember that we are charging Ninject with creating the HomeController.  When it creates the HomeController it will instantiate the instances of the Service classes defined as members for the Controller.  When it creates those Service members it will look for dependencies (things marked with Inject) in subsequent objects.  In this case, it notes a dependency on the AnimeManagerEntities data context instance.

So at this point we could leave this and the application will work just fine.  However, there is a problem with the default way Ninject handles this injection, in our case each service member will gain a UNIQUE instance of the data context.  We do not want this, we want the service members to share a common data context.  We can do this by configuring the bindings within Ninject.  The code below is the custom controller factory that we showed above:

   1:  public class NinjectCustomControllerFactory : DefaultControllerFactory
   2:  {
   3:       protected override IController GetControllerInstance(Type controllerType)
   4:       {
   5:            IKernel kernel = new StandardKernel(new MVCModule());
   6:            return (IController)kernel.Get(controllerType);
   7:       }
   8:  }

I want to point out line 8 and what is being passed to StandardKernel, this is a custom class whose implementation is shown below:

   1:  public class MVCModule : StandardModule
   2:  {
   3:       public override void Load()
   4:       {
   5:            Bind().ToSelf().Using();
   6:        }
   7:  }

So, this is a demonstration of setting up bindings in Ninject.  In a nutshell this says, “for any object of type ObjectContext bind to yourself and use one ONE instance per web request.  This fixes the problem of the multiple data contexts in a request.

So lets review what happens with this solution: the MVC framework will read the URL provided and load a controller based on it.  We have overridden the default controller factory and used one of own which uses Ninject to create the controller instances. Because we are using Ninject and we have decorated our dependencies with the Inject attribute Ninject will cascade through the objects involved and fulfill any dependencies.  In the case of Entity Data Context instance we have a special case where we want to ensure there is only one context instance used per request.  The controller classes use the Service classes to talk to the models, those controllers then pass their data to the view layer where everything is displayed to the user.  We have a low degree of coupling and a high degree of testability.

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Advertisements

2 thoughts on “Dependency Injection with MVC Framework (part 2)

  1. Nice post. One note: you shouldn't be creating a new kernel each time you create a new controller. Since a controller is activated each time a request comes in, creating a new kernel is unnecessary overhead. Instead, have your HttpApplication create the kernel at app startup, and then re-use it for each controller activation. Check out Ninject.Framework.Mvc (in Ninject 1) or Ninject.Web.Mvc (in Ninject 2) for an example.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s