The Usage of Ninject

Reference: By Adam Freeman – Pro ASP .NET MVC 4 4th Edition

As has mentioned in the previous post, the idea of dependency injection is to decouple the components in our MVC applications. Here is the example that we will explain how to use the Dependency Injection Container–Ninject.

The problem

Our simple example project relies on tightly-coupled classes: the ShoppingCart class is tightly coupled to the LinqValueCalculator class and the HomeController class is tightly coupled to both ShoppingCart and LinqValueCalculator. This means that if we want to replace the LinqValueCalculator class, we have to locate the change the references in the classes that are tightly-coupled to it. This is not a problem with such a simple project, but it becomes a tedious and error-prone process in a real project, especially if we want to between different calculator implementations, rather than just replace one class with another.

Applying an Interface

We can solve part of the problem by using a C# interface to abstract the definition of the calculator functionality from its implementation. To demonstrate this, we have added the IValueCalculator.cs file to the Models folder and created the interface:

using System.Collections.Generic;
namespace EssentialTools.Models
{
    public interface IValueCalculator
    {
        decimal ValueProducts(IEnumerable<Product> products);
    }
}

Here is the implementation of the interface:

using System.Collections.Generic;
using System.Linq;
namespace EssentialTools.Models
{
    public class LinqValueCalculator : IValueCalculator
    {
        public decimal ValueProducts(IEnumerable<Product> products)
        {
            return products.Sum(p => p.Price);
        }
    }
}

The interface allows us to break the tight-coupling between the ShoppingCart and LinqValueCalculator class:

using System.Collections.Generic;
namespace EssentialTools.Models
{
    public class ShoppingCart
    {
        private IValueCalculator calc;
        public ShoppingCart(IValueCalculator calcParam)
        {
            calc = calcParam;
        }
        public IEnumerable<Product> Products { get; set; }
        public decimal CalculateProductTotal()
        {
            return calc.ValueProducts(Products);
        }
    }
}

We have made some progress, but C# requires us to specify the implementation class for an interface during instantiation, which is fair enough because it needs to know which implementation class we want to use. This means that we still have a problem in the Home controller when we create the LinqValueCalculator object:

...
public ActionResult Index()
{
    IValueCalculator calc = new LinqValueCalculator();
    ShoppingCart cart = new ShoppingCart(calc) { Products = products };
    decimal totalValue = cart.CalculateProductTotal();
    return View(totalValue);
}
...

Our goal with Ninject is to reach the point where we specify that we want to instantiate an implementation of the IValueCalculator interface, but the details of which implementation is required are not part of the code in the Home controller.

Getting Started with Ninject

There are three stages to getting the basic Ninject functionality working:

using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;
using Ninject;
namespace EssentialTools.Controllers
{
    public class HomeController : Controller
    {
        private Product[] products = {
            new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
            new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
            new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
            new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
        };

        public ActionResult Index()
        {
            IKernel ninjectKernel = new StandardKernel();
            ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
            IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();
            ShoppingCart cart = new ShoppingCart(calc) { Products = products };
            decimal totalValue = cart.CalculateProductTotal();
            return View(totalValue);
        }
    }
}

As is shown above, the first stage is to prepare Ninject for use. We need to create an instance of a Ninject kernel:

IKernel ninjectKernel = new StandardKernel();

The second stage is to set up the relationship between the interfaces in our application and the implementation classes we want to work with:

ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();

This statement tells Ninject that when it is asked for an implementation of the IValueCalculator interface, it should service the request by creating a new instance of the LinqValueCalculator class.

The last step is to actually use Ninject:

IValueCalculator calc = ninjectKernel.Get<IValueCalculator>();

The type parameter used for the Get method tells Ninject which interface we are interested in and the result from this method is an instance of the implementation type we specified with the To method a moment ago.

Setting up MVC Dependency Injection

The result of the three steps we showed you in the previous listing is that the knowledge about which implementation class should be used to fulfill requests for the IValueCalculator interface has been set up in Ninject. But, of course, we have not improved our application any, because that knowledge is still defined in the Home controller. In the following sections, we will show you how to embed Ninject at the heart of the example MVC application, which will allow us to simplify the controller, expands the Ninject influence has so that it works across the app.

Creating the Dependency Resolver

The first change we are going to make is to create a custom dependency resolver. The MVC Framework uses a dependency resolver to create instances of the classes it needs to service requests. By creating a custom resolver, we ensure that Ninject is used whenever an object is going to be created.

Here we add a new folder to the example project called Infrastructure and add a new class file called NinjectDependencyResolver.cs:

using System;
using System.Collections.Generic;
using System.Web.Mvc;
using Ninject;
using Ninject.Parameters;
using Ninject.Syntax;
using System.Configuration;
using EssentialTools.Models;
namespace EssentialTools.Infrastructure
{
    public class NinjectDependencyResolver : IDependencyResolver
    {
        private IKernel kernel;
        public NinjectDependencyResolver()
        {
            kernel = new StandardKernel();
            AddBindings();
        }
        public object GetService(Type serviceType)
        {
            return kernel.TryGet(serviceType);
        }
        public IEnumerable<object> GetServices(Type serviceType)
        {
            return kernel.GetAll(serviceType);
        }
        private void AddBindings()
        {
            kernel.Bind<IValueCalculator>().To<LinqValueCalculator>();
        }
    }
}

The MVC Framework will call the GetService or GetServices methods when it needs an instance of a class to service an incoming request. The job of a dependency resolver is to create that instance—a task that we fulfill by calling the Ninject TryGet and GetAll methods. The TryGet method works like the Get method we used previously, but it returns null when there is no suitable binding, rather than throwing an exception. The GetAll method supports multiple bindings for a single type, which is used when there are several different service providers available.

Our dependency resolver class is also where we have set up our Ninject bindings. In the  AddBindings method, we use the Bind and To methods to set up the relationship between the IValueCalculator interface and the LinqValueCalculator class.

Register the Dependency Resolver

We have to tell the MVC Framework that we want to use our own dependency resolver, which we do by
modifying the Global.asax.cs file:

using EssentialTools.Infrastructure;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Routing;
namespace EssentialTools
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            DependencyResolver.SetResolver(new NinjectDependencyResolver());
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
}

With this addition, Ninject will be offered the opportunity to create any object instance that the MVC Framework requires, putting DI right in the core of our example MVC application.

Refactoring the Home Controller

The final step is to refactor the Home controller so that it takes advantage of the facilities we set up in the previous sections:

using EssentialTools.Models;
using System.Web.Mvc;
using System.Linq;
namespace EssentialTools.Controllers
{
    public class HomeController : Controller
    {
        private Product[] products = {
            new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
            new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
            new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
            new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
        };
        private IValueCalculator calc;
        public HomeController(IValueCalculator calcParam)
        {
            calc = calcParam;
        }
        public ActionResult Index()
        {
            ShoppingCart cart = new ShoppingCart(calc) { Products = products };
            decimal totalValue = cart.CalculateProductTotal();
            return View(totalValue);
        }
    }
}

The main change we have made here is to add a class constructor that accepts an implementation of the IValueCalculator interface. We have not specified which implementation we want to work with, and we have added an instance variable called calc that we can use to refer to the IValueCalculator we receive in the constructor throughout the controller class.

The other change we have made is to remove any mention of the Ninject code or the LinqValueCalculator class—we have broken the tight-coupling between the HomeController and LinqValueCalculator class at last.

The best benefit we get is that we only have to modify our dependency resolver class when we want to replace the LinqValueCalculator with another implementation, because this is the only place where we have to specify the implementation that is used to satisfy requests for the IValueCalculator interface.

Leave a Reply

Your email address will not be published. Required fields are marked *