简体   繁体   中英

Ninject in an n-tier application

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.

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 NinjectModule s
    • 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 NinjectModule s
  • BusinessLayer.dll
    • contains a bunch of NinjectModule s
  • DataLayer.dll
    • contains a bunch of NinjectModule s

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM