23 March 2013

Customizing the Ninject kernel configuration for use with WCF proxies

On several of our projects, we are happily using Ninject for our DI needs. Some service implementations we wrote actually call other services using WCF proxies created through an IChannelFactory. Those proxies are injected as dependencies by Ninject where required, and their lifetime is controlled using the InRequestScope() extension for ASP.NET MVC controllers or WCF services.

However, one thing that worried me was the well-known fact that Dispose() cannot be called on proxies without paying some attention to exceptions (see here). Unfortunately (and quite normally), the Ninject StandardKernel is not aware of the gory details of cleaning up correctly WCF objects implementing ICommunicationObject, like proxies. Indeed, as proxies also implement IDisposable, they are by default taken care of by the DisposableStrategy of Ninject, which does a simple call to Dispose() when the object’s lifetime is over.

Fortunately, the Ninject architecture is highly modular and it is very easy to add, remove or replace components in the kernel. A Ninject kernel implements the IKernel interface, and Ninject provides an abstract base class and a full implementation (the StandardKernel).

A kernel internally uses a set of components that provide implementations of various Ninject interfaces it needs. The actual implementations to be used are chosen in the AddComponents() method shown above. The kind of components that take care of object setup and teardown are the ones that implement the IActivationStrategy interface, like the DisposableStrategy, shown below.

The StandardKernel uses a set of activation strategies that are brought into it in its AddComponents() method:

Given this design, it is very easy to replace the DisposableStrategy by a new one that is aware of ICommunicationObject behavior:

We then derive from StandardKernel (or from KernelBase) to use the new activation strategy:

Et voilĂ ! No more nasty CommunicationObjectFaultedException when trying to Dispose of a faulted channel. By the way, this is also how we can remove things we do not need from the StandardKernel. In our case, we know we are going to use exclusively construction injection (no method or property injection), and we will not need support for using one of the Ninject object lifecycle interfaces (IStartable, IInitializable). Therefore we removed the components (activation and planning strategies) dealing with these things from our custom kernel. This is also a nice way to enforce those design decisions (constructor injection only, no dependency on Ninject lifecycle interfaces), just by simply removing the features from the container itself.

As a conclusion, I must say that I really like Ninject. Its clear design makes it easy to use, extend, and customize. It maybe consumes a few more CPU cycles than other DI containers, but it does its job very well, and is an ideal choice to introduce DI in a team that is not familiar with the concept.

No comments: