Previously we discussed getting started with Ninject and dependency injection in general. In the getting started post, we introduced a fictitious example application. This application defined concrete implementation for the following interfaces:
- IFileSystem – Abstraction of file system access. This wrapper enabled a cleaner automated testing solution.
- ILogger – Abstraction of logging.
These abstractions allowed the creation of real and fake implementations. These abstractions enable a cleaner approach to automated testing by using in-memory fakes.
There are concurrency issues with the previously implementation. For example, two instances of IFileSystem can exist. Method calls on these from two separate threads could be disastrous (or at least provide a very difficult to debug issue). How do we solve this?
Normally, we would look to our patterns tool box and determine this is a good candidate for using a singleton pattern. However, this would to dependent classes that are difficult to test. So, how do we solve this?
IOC Container Life Cycle Management
Most Inversion of Control (IOC) containers provide some mechanism for life-cycle management. The IOC container not only creates the instance, it manages the life time for the instance. Typically this is done by associated the instance with another object. When the other object no longer exists, then a new instance is created.
For example, we probably want the life time of our ILogger and IFileSystem instances to be associated with the life time of the IOC container (application life time). There may be other instances where you want the life time of your object to be associated to a particular thread.
Ninject provides life cycle management via implementation of the IBindingInSyntax generic interface.
The available methods are as follows:
- InScope – This method accepts a delegate (to be more precise a ‘Func’) that defines the object associated with the life time of the instance. As long as the object returned by the delegate remains alive (not garbage collected), the associated instance is returned. Otherwise a new instance is activated and the association begins again. This is the most generic of all the scoping methods. The following methods are helper methods that under-the-hood handle the object association.
- InTransientScope – A new instance is activated for every request. This is the default option if no other methods are called. Same as ‘InScope( c => null)’.
- InThreadScope – A new instance will be activated on a per thread basis. Same as ‘InScope( c => System.Threading.Thread.CurrentThread )’
- InSingletonScope – A single instance is activated for all subsequent requests. Same as ‘InScope( c=> c.Kernel )’
- InRequestScope – A new instance is activated per HttpContext. Same as ‘InScope( c => System.Web.HttpContext.Current )’
Allowing Ninject to manage to enforce the ‘singleton’ for our instances of ‘ILogger’ and ‘IFileSystem’ can be done by modifying our ‘NinjectModel’ to the following:
public class ScopedModule : NinjectModule
public override void Load()
// Get this from an external file.
const string logFile = @"c:tempdelete_melogfile.txt";
Comparing this to our previous module instance we see that we added a ‘.InSingletonScope()’ call to the binding of both the IFileSystem and ILogger instances. This is our production instance (we are returning the file version of ILogger and IFileSystem). The test bed version, will not be modified. At this time, I want a new instance of FakeLogger and FakeFileSystem for each request.
Leveraging Ninject to control the life cycle of your objects is as simple as adding scoping calls to your NinjectModule. In the singleton scope case, this side steps the automated test issues that arise with static instances. The additional code is kept to a minimum and isolated to the NinjectModule. This is an example of how the centralized configuration of the IOC container allows elements to be reconfigured easily.