3

Whilst the concepts of DI and IoC containers are fairly straight forward I seem to be struggling with the implementation. I have a four-tier application in which the UI Layer to Service Layer uses IoC and seems to be working perfect but the Service Layer to Business Layer is an absolute pain.

I've read many articles specifically Ninject and Class Libraries but I'm still having trouble implementing correctly. I'm hoping you kind folk can point me in the right direction...

Typical hierarchical flow: UI Layer > Service Layer > Business Layer > Data Layer

So If may show the implementation of my UI Layer to Service Layer:

    private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<IApiAuthorizationService>().To<ApiAuthorizationService>();
    } 

    public class DebateController : ApiController
    {
        private IApiAuthorizationService _iApiAuthorizationService;
        public DebateController(IApiAuthorizationService iApiAuthorizationService)
        {
            _iApiAuthorizationService = iApiAuthorizationService;
        }
     }

As you can see the UI Layer is a WebApi project that injects the IApiAuthorizationService nothing terribly complicated here.

So once ApiAuthorizationService is constructed it points to many repositories but for now I'll add a snippet of just the one.

We're in the Service Layer now which references the Business Layer:

    public class ApiAuthorizationService : IApiAuthorizationService
    {
        private IApiAuthTokenRepository _iApiAuthTokenRepository;
        public ApiAuthorizationService(IApiAuthTokenRepository iApiAuthTokenRepository)
        {
            _iApiAuthTokenRepository = iApiAuthTokenRepository;
        }
     }

At this point I've installed Ninject on the Service Layer and also created a class that will create the bindings:

public class Bindings : NinjectModule
{
    public override void Load()
    {
        Bind<IApiAuthTokenRepository>().To<ApiAuthTokenRepository>();
    }
}

Basically I'm stuck at this point and not sure where to go, again I've read many posts but many use Console Applications to demonstrate and a Class Library does not have an entry point. What I was thinking was to add a Startup.cs to initialize the bindings.

Could anyone point me in the right direction with some demo code?

Thank you.

Tez Wingfield
  • 2,129
  • 5
  • 26
  • 46
  • Where are you initializing the container? Can you show the code? – Yacoub Massad Apr 06 '16 at 20:36
  • @YacoubMassad Again UI to Service is fine...It's service to business I'm unsure about at the moment. I do not initialize the container because that's the question...Where to do so in a Class Library. – Tez Wingfield Apr 07 '16 at 08:03
  • 2
    You shouldn't use the container in class libraries. You should use it only in the [Composition Root](http://blog.ploeh.dk/2011/07/28/CompositionRoot/). – Yacoub Massad Apr 07 '16 at 08:27
  • 1
    Related: https://stackoverflow.com/questions/9501604/ioc-di-why-do-i-have-to-reference-all-layers-assemblies-in-entry-application – Steven Apr 07 '16 at 09:18
  • @Steven thank you +1. It did help clear a few things up. – Tez Wingfield Apr 08 '16 at 13:30

2 Answers2

0

Composition Root

Primarily the location for instanciating a DI container and configuring it should be the application entry point. Meaning that you dont, ever, instanciate a DI container in a library. Also means, that the entry point needs to know all dependencies and configures them accordingly. Mark Seeman has a blog post about the principle: Composition Root.

"Good Enough" Approach

Now, in most project's i've violated this approach by some degree - which has advantages and disadvantages. Ninject (and Autofac...) feature the concept of modules. A module is a set of bindings / registration, so it's a (partial) container configuration specification. These can be added to the libraries and picked up in the composition root. The concept is documented here.

Example:

  • WpfApplication.exe
    • contains a bunch of NinjectModules
    • represents composition root with something along the lines of: var kernel = new StandardKernel() and kernel.Load(AppDomain.CurrentDomain.GetAssemblies());
  • ServiceLayer.dll
    • contains a bunch of NinjectModules
  • BusinessLayer.dll
    • contains a bunch of NinjectModules
  • DataLayer.dll
    • contains a bunch of NinjectModules

When should you not do so?

  • When any of these *.dlls are supposed to be used in multiple projects / softwares - it's likely that depending on the project you'll need different bindings (container configuration). And this can generally not be reused (also see here).
  • When you plan to change containers frequently this will impose more work

In these cases you should strictly adhere to Mark's Composition Root concept.

BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • Fantastic answer, Having read the article on Composition Root...I'm slightly concerned that having to have every assembly available to the entry point application which is WebApi a bit daunting and very much against what I'm trying to achieve - Hierarchical pattern and a decoupled system. It basically stated to include all assembles in the UI layer... Is this not counter productive? – Tez Wingfield Apr 07 '16 at 10:24
  • @TezWingfield i think this is best covered by the link @ Steven shared as a comment on your question: https://stackoverflow.com/a/9503612/684096 – BatteryBackupUnit Apr 07 '16 at 11:52
  • Ok, Thank you. Adhering to the "Composition Root" technique is not something I'd like to build/Adhere too. Having said that, your answer is still most fitting and correct. I may just end up loading the assemblies' in at run time. I very much appreciate your time. – Tez Wingfield Apr 08 '16 at 13:34
-1

You could do what you're doing, and create a single location where services are registered. That would work. But here's a high level approach that also works if you think there is a benefit to your application.

The general idea is a layer between the dependency injection library and your application. Let's call this layer IServiceContainer.

public class ServiceContainer : IServiceContainer
{
  // internally manages ninjects kernel via methods such as...
  public void RegisterType<TService, TInstance>();
  public void RegisterTypes(Assembly assembly);

  // ultimately, this just segregates Ninject from your app so there are no Ninject dependencies
}

Now, you could manually add all the things you want to register via IServiceContainer.RegisterType, or, you do something a bit more automatic with attributes, like....

[ServiceRegistration(typeof(IApiAuthorizationService))]
public class ApiAuthorizationService : IApiAuthorizationService
{
    private IApiAuthTokenRepository _iApiAuthTokenRepository;
    public ApiAuthorizationService(IApiAuthTokenRepository iApiAuthTokenRepository)
    {
        _iApiAuthTokenRepository = iApiAuthTokenRepository;
    }
}

The implementation of IServiceContainer.RegisterTypes would scan all the types in it for the ServiceRegistrationAttribute. For each of them, now you can register the service type and implementation type automatically via IServiceContainer.RegisterType()

This can be taken as far or not as needed.

As for how this addresses your question of "using in an N-tier application"... You could provide a single interface implementation in each assembly that knows how to register all of it's needs and that implementation might in turn call the same interface implementation from another assembly it depends on. In this way, you allow each assembly to forward along the registration of it's dependent assemblies. It's a long term strategy that might present you with a couple useful ideas.

Here's a rough idea of what that could look like (remember, you would have one of these in each assembly)...

public class AutoRegistration: IAutoRegistration
{
    public void Register(IServiceContainer container)
    {
        // where the type of Add<> is IAutoRegistration
        container.Add<SomeDependentNamespace.AutoRegistration>();
        container.Add<SomeOtherDependentNamespace.AutoRegistration>();
    }
}

The IServiceContainer would simply collect up all the distinct IAutoRegistration's that were added (it knows the assembly at that point from each TAutoRegistration in .Add() and can scan the types for the attribute as shown earlier and register the types one by one.

Trevor Ash
  • 623
  • 4
  • 8
  • Thank you for your very informative answer. I'm still curious and unsure about the approach. I'm not sure where I see this code sitting on the moment: var kernel = new StandardKernel(); Register(kernel); // register the objects var apiAuthTokenRepository = kernel.Get(); // create instance – Tez Wingfield Apr 07 '16 at 08:10
  • These features are already included in lots of DI containers, including Ninject - no need to "roll your own". Furthermore, here's some thoughts on "common abstraction": [Conforming Container](http://blog.ploeh.dk/2014/05/19/conforming-container/) – BatteryBackupUnit Apr 07 '16 at 08:57