K Cartlidge

C#/DotNet. Go. Node. Python/Flask. Elixir.

Using NInject with WebAPI 2

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

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

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.

YMMV.