K Cartlidge

Using NInject with WebAPI 2

27 June, 2017

Dependency injection is something that is usually set up once when a project begins and then not repeated until another one comes along - by which time I’ve forgotten what I did before and have to start searching again.

This is, therefore, a quick post to give me a single place to look when I next need to do it.

Solution structure

One of the main goals of DI is to reduce knowledge requirements. In effect, as your main running thread (for example) should be given it’s dependencies automatically (hence DI) there is no need for it to need to know where they are coming from, which means the main project should not have references to the projects holding the injected others.

At the same time, it needs to know enough about them in order to both accept and use them.

The easiest and clearest way to do this is to have a structure similar to this (assuming the solution is named MFW - don’t ask why):

Project Contains References
MFW.Interfaces All interface definitions, with each project having it’s own subfolder. MFW.Models
MFW.Models All model definitions, with folders for each type (eg ViewModels, DataModels).  
MFW.IOC Handles registering all non-WebAPI dependencies. Everything except MFW.API
MFW.Services Business logic. MFW.Interfaces, MFW.Models
MFW.Data Data layer and logic. MFW.Interfaces, MFW.Models
MFW.API Web API. MFW.Interfaces, MFW.Models, MFW.IOC
  • It isn’t unusual to treat Models as another form of contract like Interfaces and to have the model definitions in a Models subfolder of the main Interfaces project.

Given this setup, via constructor injection based on the interfaces nothing but the IOC project ever needs to know anything about concrete implementations. Cross-project references are minimised and coupling reduced.

Adding NInject to the solution

Using NUget, there are a few packages to install. If you do them in the following order, some of the later packages will already be partially included by the time you reach those steps.

Package Projects
NInject.Web.WebApi MFW.API
Ninject.Web.Common.WebHost MFW.API
NInject MFW.API, MFW.IOC
NInject.Extentions.Conventions MFW.IOC
  • NInject is going into two projects for a reason. The main IOC needs it, obviously, but the API also needs it as it will be registering it’s container with the WebAPI framework for automatic controller construction.

Code changes to make it happen

First you need to add some code to the MFW.IOC project to do all the registrations.

// MFW.IOC\DI.cs

using System.Reflection;
using Ninject;
using Ninject.Extensions.Conventions;
using MFW.Services;

namespace MFW.IOC
{
  public static class DI
  {
    public static void AddRegistrations(IKernel kernel)
    {
      // Find the assembly containing SiteService, which would be
      // MFW.Services, then register everything in that assembly.
      RegisterAssemblyTypes(kernel, typeof(SiteService).Assembly);

      // Same again for the data layer.
      RegisterAssemblyTypes(kernel, typeof(SiteRepository).Assembly);
    }

    private static void RegisterAssemblyTypes(IKernel kernel, Assembly assembly)
    {
      kernel.Bind(x => x.From(assembly).SelectAllClasses().BindAllInterfaces());
    }
  }
}

The above class should be expanded to add registrations for every project containing dependencies. It works by looking for a single type that is known to exist in a project, using that to find the assembly for that project, then registering everything in that assembly.

Now update the (already existing) App_Start\NinjectWebCommon.cs file to fill in the empty method named as below with the contents shown:

// MFW.API\App_Start\NinjectWebCommon.cs

using MFW.IOC;

private static void RegisterServices(IKernel kernel)
{
  // Add this line:
  DI.AddRegistrations(kernel);
}

Now finally it’s a simple matter of making use of constructor injection:

// MFW.API\Controllers\SiteController.cs

using MFW.Interfaces.Models;
using MFW.Interfaces.Services;
using System.Web.Http;

namespace MFW.API.Controllers
{
  public class SiteController : ApiController
  {
    private ISiteService _siteService;

    public SiteController(ISiteService siteService)
    {
      _siteService = siteService;
    }

    // GET api/site
    public SiteViewModel Get()
    {
      return _siteService.Get();
    }
  }
}

Notes

There are things you can do which I haven’t bothered doing as they don’t add much benefit for such a one-off task.

  • You could remove the NInject references from MFW.API by deferring to the MFW.IOC.DS class for everything and passing it the required details for it register itself with the WebAPI framework. However the integrations already provided by the packages do so much of this for you there’s no point changing it for the sake of one extra set of references to the IOC package.
  • You could be cleverer about finding the assemblies to bind in MFW.IOC.DI.AddRegistrations, but the current way is simple enough as the only requirement is an extra line (with a reference to a known class) when a new injectable project is added. This is already far easier than registering types independently and only imposes overhead when new projects are added, which is rare.
  • You could reduce unnecessary type registrations by filtering the types inside MFW.IOC.DI.RegisterAssemblyTypes, but as these assemblies are usually your own they are mostly intended to be registered anyway. Using this method of registration for framework libraries that contain thousands of types in order to save adding a handful in manually may, however, be overkill - they could be registered individually as per NInject examples.

YMMV.