Dependency Injection with MVC Framework (part 1)

One of the problems I have had in the past with MVC is my data layer when I use one of Microsoft’s ORM tools such as Linq2Sql or Entity Framework.  Here at RCM we tend to use the Kinetic Framework which is an ORM tool that is built on CodeSmith Templates and was developed in house.  However, as we continue to move forward there is a desire to choose a different direction due to new features and better support for existing databases.

One of the ORM tools I decided to look at and see how I could potentially use Entity Framework with the ASP .NET MVC Framework.  It went through quite a few evolutions but we ended up with a pretty decent solution, below are a few of the requirements the end solutions must meet:

  • We should have the ability to load data on demand either through eager loading or, more preferably, lazy loading.
  • We should be able to easy decouple our model layer from our web layer so that we can reuse the model layer in any context
  • We should be able to test the model layer independent from the View and Controller layers
  • The method should require minimal effort from developers to wire up the system and should not call them to make huge concessions with coding styles.
  • At most one entity context instance can be created per request and the reference must be thread safe (no static variables). It must also be destroyed at the end of the request

To begin, in my reading and watching of MVC tutorials I have seen the following code snippet used many times:

public class TestController : Controller
{
     private readonly AnimeManagerEntities entityContext = new AnimeManagerEntities();
     public ActionResult Index()
     {
          return View();
     }
}

So what this gives us is a entityContext for each Controller that we create.  In fact, this is not a bad idea, you could derive from Controller and create a public property that is given to all of your Controllers.

Using this approach you certainly can easily use the context variable to query the various data sets in the database.  If you trust Microsoft, you can have faith that the context will be closed, and because the context remains open during the call to View() which will allow lazy loaded properties in the View to be loaded on the fly.

So what is the problem with this approach…

Well its not very reusable, which is a huge problem for any large applications.  Consider if I had two actions, in this case we will have them in the same controller, each of which I need to grab a Series from the database based on a given id:

.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; }

   1:  public class TestController : Core.ControllerBase
   2:  {
   3:       public ActionResult Index()
   4:       {
   5:            return View();
   6:       }
   7:   
   8:       public ActionResult Edit(int id)
   9:       {
  10:            return View(EntityContext.Series.Include("Studio")
  11:                 .FirstOrDefault(s => s.SeriesId == id));
  12:       }
  13:   
  14:       public ActionResult View(int id)
  15:       {
  16:            return View(EntityContext.Series.Include("Studio")
  17:                 .FirstOrDefault(s => s.SeriesId == id));
  18:       }
  19:  }

You will notice ControllerBase which provides the public property EntityContextControllerBase also has a custom destructor which disposes of the entity context variable.

The use of Include targets named relationships from the .edmx file and provides for eager loading, so that Series instances are extracted with the Studio reference loaded.

But the major problem here is code repetition.  In this simple example the same line of code is repeated twice. Imagine if this was a much more complex process with 20 or 30 actions.  What if we had to use this line all over the application and we suddenly wanted to change the order of Includes, or the primary key, or anything.  This harkens back to the reason why we create Model layers, to centralize the way we do things and improve the maintainability of code.  As I said, this solution will work fine for simple scenarios, but to use it with a larger project a different pattern must be used.

In addition, while the Controller is testable, the model layer is not.  This approach bleeds model and controller testing together as the Model is not properly decoupled from the Controller. Remember, the Controller shouldn’t care how it gets the data from the mode, it just wants it. In the same respect, the View doesn’t care how it was selected just that it was and the Controller provides the appropriate data that it needs.

So what we really need to do is centralize our data access logic so that we can test it apart from our controller logic. By doing so we can decouple the model layer and use it in other projects.

Now let us step back very quickly and understand something.  Regardless of the path we take we will want the EntityContext to come from the controller layer.   Saying this sounds like a contradiction to what I just said, it isn’t, let me explain.

If we are feeding the context from the controller layer to the model layer, we are creating a dependency, not a rule. In testing we can certainly feed our model layer with an entity context. The entity context is NOT, however, tied to the controller layer as it was in the example above.  Given this principle we can rewrite the code as such:

   1:  public class TestController : Core.ControllerBase
   2:  {
   3:       public ActionResult Edit(int id)
   4:       {
   5:            return View(Series.GetSeries(EntityContext, id));
   6:       }
   7:   
   8:       public ActionResult View(int id)
   9:       {
  10:            return View(Series.GetSeries(EntityContext, id));
  11:       }
  12:  }
  13:   
  14:  // And in Series.cs
  15:  public partial class Series
  16:  {
  17:       public static Series GetSeries(AnimeManagerEntities context, int id)
  18:       {
  19:            return context.Series.Include("Studio")
  20:                 .FirstOrDefault(s => s.SeriesId == id);
  21:       }
  22:  }

This is not a bad solution. It is very testable and the code is centralized in what amounts to a basic ActiveRecord pattern.  However, we are now requiring developers to pass an instance of their EntityContext to EVERY SINGLE DATA METHOD.  By passing the reference to this method we are implying in code, that the developer must have a reference and it violates our tenant of not making it harder to code.  Furthermore, this adds unnecessary clutter to the method calls.

So we are close, the problem we want to solve now is how can we best get our reference into the model layer automatically without the developer worrying about it through a clean and well separated means that still permits loose coupling and high testability.  There are likely thousands of ways to achieve this, but I will show you an an example how I did it with Ninject Dependency Injection in the next part.

.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

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