Dependency injection across multiple layers with NInject

The purpose of this post is to discuss how to perform dependency injection across a multi-layered application without introducing extra dependencies between projects. More specifically, I am going to create a solution in C# with 3 different projects

  1. TopLayer
  2. MiddleLayer
  3. BottomLayer

and I want to use NInject accross all 3 layers, without the TopLayer knowing about the BottomLayer. This means there will be no reference between the TopLayer and the BottomLayer.

I will demonstrate using a console application but the same solution can be applied to any project where one can use NInject, including ASP.NET MVC applications and WPF applications.

I create a new solution named MultiLayerApp and within it a C# console application project called TopLayer.

The Program.cs file contains just a simple class that prints a message.

namespace MultiLayerApp.TopLayer
{
    public class Program
    {
        static void Main(string[] args)
        {
            System.Console.WriteLine("This is the top layer.");

            System.Console.WriteLine("Press enter to continue...");
            System.Console.ReadLine();
        }
    }
}

Next, I create a C# library called MiddleLayer and I reference it from the TopLayer.

Finally, I create a C# library called BottomLayer and we reference it from the MiddleLayer.

Notice that the TopLayer does not know that the BottomLayer exists.

Now, starting from the bottom layer, let’s create some simple interfaces and classes implementing them. Inside the BottomLayer I create an interface

namespace MultiLayerApp.BottomLayer
{
    public interface IBottomClass
    {
        string GetMessage();
    }
}

together with a concrete class implementing this interface

namespace MultiLayerApp.BottomLayer
{
    public class BottomClass : IBottomClass
    {
        public string GetMessage()
        {
            return "This is a message from the bottom layer.";
        }
    }
}

On the middle layer I wrap the BottomClass inside a MiddleClass

using MultiLayerApp.BottomLayer;

namespace MultiLayerApp.MiddleLayer
{
    public class MiddleClass : IMiddleClass
    {
        private IBottomClass _bottomClass;

        public MiddleClass(IBottomClass bottomClass)
        {
            _bottomClass = bottomClass;
        }

        public string GetMessage()
        {
            return _bottomClass.GetMessage();
        }
    }
}

that implements an interface IMiddleClass

namespace MultiLayerApp.MiddleLayer
{
    public interface IMiddleClass
    {
        string GetMessage();
    }
}

Finally, I wrap the MiddleClass inside a TopClass on the TopLayer

using MultiLayerApp.MiddleLayer;

namespace MultiLayerApp.TopLayer
{
    public class TopClass : ITopClass
    {
        private IMiddleClass _middleClass;

        public TopClass(IMiddleClass middleClass)
        {
            _middleClass = middleClass;
        }

        public string GetMessage()
        {
            return _middleClass.GetMessage();
        }
    }
}

TopClass implements the interface

namespace MultiLayerApp.TopLayer
{
    interface ITopClass
    {
        string GetMessage();
    }
}

In all wrappers I inject the wrapped class via the constructor. The solution now looks like this

Next, I am going to install NInject. Right click the TopLevel project and select Manage NuGet Packages...

On the next screen search for NInject and install it.

I create a module within the TopLayer that inherits from NinjectModule like this

using Ninject.Modules;

namespace MultiLayerApp.TopLayer
{
    public class TopModule : NinjectModule
    {
        public override void Load()
        {
            Bind<ITopClass>().To<TopClass>();
        }
    }
}

where I declare that an instance of the TopClass needs to be injected wherever an ITopClass is needed.

I modify the Program.cs to instantiate a StandardKernel passing in the TopModule.

using Ninject;

namespace MultiLayerApp.TopLayer
{
    public class Program
    {
        static void Main(string[] args)
        {
            IKernel kernel = new StandardKernel(new TopModule());

            var topClass = kernel.Get<ITopClass>();
            var message = topClass.GetMessage();
            System.Console.WriteLine(message);
        }
    }
}

I then ask from the container to give me an instance of ITopClass, I call the GetMessage method on it and print the result on the screen. The solution now looks like this

If I try to compile the application it will compile, but when I run it I get the following error

Unhandled Exception: Ninject.ActivationException: Error activating IMiddleClass
No matching bindings are available, and the type is not self-bindable.
Activation path:
  2) Injection of dependency IMiddleClass into parameter middleClass of constructor of type TopClass
  1) Request for ITopClass

Suggestions:
  1) Ensure that you have defined a binding for IMiddleClass.
  2) If the binding was defined in a module, ensure that the module has been loaded into the kernel.
  3) Ensure you have not accidentally created more than one kernel.
  4) If you are using constructor arguments, ensure that the parameter name matches the constructors parameter name.
  5) If you are using automatic module loading, ensure the search path and filters are correct.

If I add the binding for the MiddleClass in TopModule.cs and run again I get the same error but for the BottomClass that has no binding. If I try to add a binding for it, I realise that the TopLayer has no reference to the BottomLayer and this is not possible.

I abandon this plan and remove the bindings for both MiddleClass and BottomClass in TopModule.cs.

So, how can I do dependency injection without the top layer knowing about the bottom layer?

To achieve this I am going to introduce a fourth project that we will call DependencyResolver. DependencyResolver will reference all layers apart from the top layer. TopLayer will reference DependencyResolver.

The references between our projects look like this now

I install NInject in DependencyResolver as well. I implement two classes deriving from NinjectModule in this project. One with the bindings for the middle layer

using Ninject.Modules;
using MultiLayerApp.MiddleLayer;

namespace MultiLayerApp.DependencyResolver
{
    public class MiddleModule : NinjectModule
    {
        public override void Load()
        {
            Bind<IMiddleClass>().To<MiddleClass>();
        }
    }
}

and one with the bindings for the bottom layer

using Ninject.Modules;
using MultiLayerApp.BottomLayer;

namespace MultiLayerApp.DependencyResolver
{
    public class BottomModule : NinjectModule
    {
        public override void Load()
        {
            Bind<IBottomClass>().To<BottomClass>();
        }
    }
}

The solution now looks like this

Finally, I just need to load the new modules in Program.cs

using System.Collections.Generic;
using Ninject;
using Ninject.Modules;
using MultiLayerApp.DependencyResolver;

namespace MultiLayerApp.TopLayer
{
    public class Program
    {
        static void Main(string[] args)
        {
            IKernel kernel = new StandardKernel(new TopModule());

            var modules = new List<INinjectModule>
            {
                new MiddleModule(),
                new BottomModule()
            };
            kernel.Load(modules);

            var topClass = kernel.Get<ITopClass>();
            var message = topClass.GetMessage();
            System.Console.WriteLine(message);
        
            System.Console.WriteLine("Press enter to continue...");
            System.Console.ReadLine();
        }
    }
}

Now if I run the program I see the message coming from the bottom layer being printed on the screen.

 

comments powered by Disqus