Home / .NET / Future of Activation blocks

Future of Activation blocks



Since I started using and working on Ninject I have never seen a good use case for Activations Blocks like they are implemented at the moment. That’s why I’m currently planning on changing their behavior to give them more sense. This blog post will explain what I have in mind at the moment.

Current usage and behavior (Ninject 3.0.0)

Ninject V3.0.0 provides kernel.CreateBlock() which returns a IResolutionRoot. Within this resolution root every type exists exactly once as a local singleton object no matter which scope is defined for the binding.

In my opinion this behavior makes no sense. There are situations where you want to have exactly one instance of a service in the application. Activations Blocks do not allow to use these services because exactly one instance of each dependency is created within the activation block. Instead of sharing the instance between multiple activation blocks a new one is created for each activation block.

Purpose of activation blocks

The second problem of activation blocks is the way they are used. In order to create one you have call the CreateBlock method on the kernel. This has two disadvantages:

  1. To create an activation block you need to know the kernel which gives you a dependency on Ninject.
  2. You will get an IResolutionRoot on which you can resolve any type of dependency which means you are using Ninject like a kind of service locator.

Because of these problems the activation block shouldn’t be used within applications. We can achieve the same behavior without having these problems with named scopes. How this is done is explained in the next chapter.

But there is situations where activation blocks are helpful. There are frameworks e.g. ASP.NET WebAPI and NServiceBus that have the concept to create a scope around a request. While integration into these types of frameworks using named scopes is possible it is much simpler to do so with an activation block. But the current implementation of the activation block where everything is a singleton object within the block is not correct an needs to be changed as described in the last part of this article.

Creating an activation block using a Named Scope

As already explained using activation blocks within an application has two disadvantages. You can get the same behavior using named scopes and factories.

Instead of creating a new block you pass a factory to the component that is responsible to start the activation block and use it whenever a new activation block is required. E.g. pass a IMyTcpRequestHandlerFactory to the IMyTcpServer class and whenever a new connection is established this factory is used to create a new IMyTcpRequestHandler.

The second step is to configure that this newly created component is the scope for other components it is depending on using a named scope and assign this scope to the dependencies:

public class MyTcpRequestHandler : IMyTcpRequestHandler
{
    public MyTcpRequestHandler(ISomeDependency someDependency) {}

    ....
}

const string TcpRequestScope = "TcpRequestScope";
Bind<IMyTcpRequestHandlerFactory>().ToFactory();
Bind<IMyTcpRequestHandler>().To<MyTcpRequestHandler>().DefinesNamedScope(TcpRequestScope);
Bind<ISomeDependency>().To<SomeDependency>().InNamedScope(TcpRequestScope);

In case you need to create multiple dependencies depending on some runtime conditions you can pass other factories to this component and use them to create them. But in this case you will have to load the Ninject.Extensions.ContextPreservation extension together with the named scope extension.

How activation blocks will behave in future

public class MyTcpRequestHandler : IMyTcpRequestHandler
{
    public MyTcpRequestHandler(IProcessingStrategyFactory processingStrategyFactory, ...) {}

    public void HandleRequest()
    {
        var contentType = this.GetTcpContentType();
        var processingStrategy = contentType.IsXml
            ? this.processingStrategyFactory.CreateXmlProcessingStrategy();
            : this.processingStrategyFactory.CreateBinaryProcessingStrategy();

        ....
    }
}

const string TcpRequestScope = "TcpRequestScope";
Bind<IMyTcpRequestHandlerFactory>().ToFactory();
Bind<IProcessingStrategyFactory>().ToFactory();
Bind<IMyTcpRequestHandler>().To<MyTcpRequestHandler>().DefinesNamedScope(TcpRequestScope);
Bind<IProcessingStrategy>().To<XmlProcessingStrategy>().InNamedScope(TcpRequestScope);
Bind<IProcessingStrategy>().To<BinaryProcessingStrategy>().InNamedScope(TcpRequestScope);

Changes to the current activation block

I’m currently planning to add a new scope InActivationBlockScope to Ninject. Only bindings with this scope type will be affected by the activation block. Within an activation block they will be local singleton objects. Exactly one instance will be created within the activation block and they will be disposed together with the activation block. Outside of an activation block they will behave like transient objets. All other scopes will behave exactly the same way within and outside of activation blocks.

With this change it will be possible to use the existing scopes within an activation block while having the possibility to scope some objects by an activation block. This will make it easy to use Ninject together with Frameworks like ASP.NET WebAPI and NServiceBus.

Currently, this change is still a proposal. Feedback is very welcome.