Sunday, December 11, 2011

Dependency Injection and Ninject (Part 2)

In a previous post we saw that we can use Dependency Injection to limit the dependency between classes. This greatly improves the maintainability of an application. In this post the Ninject dependency injector will be added to our ASP.NET MVC application. We will make our business logic and database access layer loosely coupled to the classes that access the layers. I will demonstrate how this allows for switching from a real database layer to a fake one by changing only one line of code.
First download the Ninject core library and add a reference to it from your web application project. For the integration with ASP.NET MVC you also need an extension to the core library.
Now change the Global.asax.cs file in the root of your application as follows:
using Ninject;
using Ninject.Web.Mvc;

public class MvcApplication : NinjectHttpApplication   // System.Web.HttpApplication
{
    protected override void OnApplicationStarted()
    {
        AreaRegistration.RegisterAllAreas();
        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterRoutes(RouteTable.Routes);
    }

    protected override IKernel CreateKernel()
    {
        IKernel kernel = new StandardKernel();
        kernel.Bind<ITermService>().To<TermService>();
        kernel.Bind<ITermRepository>().To<SqlServerTermRepository>).<br/>                WithConstructorArgument("connectionString", "MyConnectionString");
        return kernel;
    }
}
For examples of the service and repository classes in this code, see my previous post.

After the changes to the Global.asax.cs file even our controllers will be activated via Ninject, meaning we can expose dependencies on their constructors to request injections. What does this mean? Here is a controller that needs an ITermService instance to access business logic methods:
public class TermController : System.Web.Mvc.Controller
{
    private _ITermService _service;

    public TermController(ITermService service)
    {
        _service = service;
    }

    /// <summary>
    /// Returns a  View that shows a list of terms in the specified language.
    /// </summary>
    public ActionResult Terms(int languageId)
    {
        IList<Term> terms = _service.GetTerms(languageId);
        return View(terms);
    }
}
When this controller is instantiated it 'requests' an instance of the ITermService class and it will get one from the Ninject framework. Requests for ITermService instances have been bound to the TermService class in the CreateKernel override in Global.asax.cs. Therefore the controller receives a TermService instance, that it can use to retrieve and show a list of terms.
Note that we do not refer to the TermService class in the controller, only the more abstract ITermService interface is used. There is no explicit dependency between the controller and a specific ITermService implementation. Whenever a dependency is needed, one is requested from the dependency injection framework (in our case Ninject). This pattern is also called Inversion of Control.

The TermService class itself uses a repository that retrieves terms from a database:
public class TermService : ITermService
{
    private ITermRepository _termRepository;

    public TermService(ITermRepository termRepository)
    {
        _termRepository = termRepository;
    }

    public IList<Term> GetTerms(int languageId)
    {
        return _termRepository.GetTerms(languageId);
    }
}
Since requests for ITermRepository instances have been bound to the SqlServerTermRepository class (see the Global.asax code), the terms will be selected from a SQL Server database, assuming that the SqlServerTermRepository class does what its name says.
If we want our application to make use of a 'fake' database instead, we only have to change the one line of code that describes the Ninject binding in Global.asax.cs:
kernel.Bind<ITermRepository>().To<FakeTermRepository>();
The FakeTermRepository class could return a list of terms without ever accessing a real database, which is very useful in testing scenarios or when no real database has been setup yet.

No comments:

Post a Comment