Ever wanted to program like a ninja? A couple of days ago I stumbled upon Ninject, an open source dependency injector (DI) for .NET. The entire design of the project is hilarious, I felt like a ninja and like I’m doing something really cool and sneaky by playing around with the framework. Although using dependency injection is very common, the usage of an DI framework is something new for many developers.
Dependency Injection is an implementation of the Inversion of Control pattern. There are two possible implementations for IoC:
- Service Locator or Dependency Lookup: container provides callback functionality and lookup context, components are requested by using the locator (container) API. A dependency to the locator and API persists, central binding and contextual binding is possible.
- Dependency Injection: implementation without dependency to a container API possible, able to manage lifecycles (scope, request, singleton, thread, transient), lookup and contextual binding abilities are depending on the DI framework.
The idea behind DI and Inversion of Control is the use of the so-called “Hollywood Principle” – “don’t call us, we’ll call you!”
Simple Dependency Injection
Let me provide you an example of DI without using any DI framework. It’s a common approach, that you likely know.
When an object needs another object to operate properly, we have a dependency. In the following example we have a Service and a Client, where the Client class is tightly coupled with the Service class:
public class Service
{
public void Serve()
{
// do some serving stuff
}
}
public class Client
{
private Service _service;
public Client()
{
this._service = new Service();
}
public void Start()
{
this._service.Serve();
// do some client stuff
}
}
Dependency injection eliminates that tight coupling and makes the application more flexible and reusable:
public interface IService
{
public void Serve();
}
public class Service : IService
{
public void Serve()
{
// do some serving stuff
}
}
public class Client
{
private IService _service;
public Client(IService service)
{
this._service = service;
}
public void Start()
{
this._service.Serve();
// do some client stuff
}
}
Sample Builder-Implementation:
var client = new Client(new Service());
client.Start();
The Injection happens in the constructor, by passing the Service that implements the IService-Interface. The dependencies are assembled by a “Builder”, his responsibilities are as follows:
- knowing the types of each IService
- according to the request, feed the abstract IService to the Client
The example above uses Constructor Injection. There are the following 3 possible implementations of DI:
- Interface Injection
- Setter Injection
- Constructor Injection
What's wrong with the approaches?
Up till this point, you should no doubt realize the important role the dependency injection above. In fact, as you may have doped out, there are some issues with the approaches:
- It is hardcoded: it can't be reused across applications, due to hardcoded factories - that makes the code stringent to particular implementations
- Interface dependent: interfaces are used for decoupling the implementation and the object creation procedure
- Instantiating is custom: instantiations are very much custom to a particular implementation
- Everything is compile time: all dependent types for the objects in the instantiation process (factory) have to be known at compile time
Service Locator
A common approach to achieve assembling the Client could be the following implementation, by using service locator to avoid some of the issues described before:
public class Locator
{
private static Dictionary<Type, Type> dictionary = new Dictionary<Type, Type>();
static Locator()
{
dictionary.Add(typeof(IService), typeof(Service));
}
public object Create(Type type)
{
if ((type == null || !dictionary.ContainsKey(type)))
throw new NullReferenceException();
return Locator.CreateInstance(dictionary[type]);
}
public T Create<T>()
{
return (T)Create(typeof(T));
}
}
Usage:
var client = new Client(Locator.Create<IService>());
Extending the Locator looks like a great solution here - by using a service locator it should be possible to achieve some runtime capabilities and a clean and modular way to map interfaces to types. The more complex your requirements, the more complex would be the Locator. Implementing the ability to configure the Locator by using a XML configuration file, implement conditional operation by reflecting types, attributes and even parameter names sounds like a great solution, didn't it?
What if the Client will get some more dependencies to inject? What if that happens to the implementations of the IService too? What if that happens only to a few implementations of the IService? What about unit testing and setting up type mappings depending on the environment we are in? What about decoupling the instantiation process a bit more? What about controlling the life cycle of the created objects?
Good news here! There is already a solution to achieve those requirements: the container way :)
If you are interested in more specific information about the service locator pattern and why it should be avoided I recommend the Service Locator is an Anti-Pattern blog post from Misko Hevery.
Using Dependency Injection Containers (IOC)
IOC assigns the responsibility of creating and coupling of objects to a configurable Framework, according to a component model. The code of the object will become independent of the environment and the concrete implementation of the class, it depends on and makes particular unit-testing more easily.
There are many dependency frameworks for .NET to chose from. The following examples are done by using Ninject, which comes with the following features:
- compact and easy to understand
- lightweight, focusing the core functionalities
- fast, it takes advantage of code generation in the CLR
- doesn’t uses XML
- takes advantage of the capabilities of the IDE and IntelliSense
- supports contextual binding
Let me demonstrate you the power of IOC by implementing it to our Client/Service example:
public interface IService
{
public void Serve();
}
public class Service : IService
{
public void Serve()
{
// do some serving stuff
}
}
public class Client
{
private IService _service;
public Client(IService service)
{
this._service = service;
}
public void Start()
{
this._service.Serve();
// do some client stuff
}
}
Did you noticed that there is no change of the example at all? The difference in this example relies only on the usage of the provided classes. Like many IoC containers, Ninject uses a central object (kernel) to provide concrete implementations of dependencies at run-time. The StandardKernel is the default implementation of such an object. So lets do it the Ninject-Way:
using (IKernel kernel = new StandardKernel())
{
kernel.Bind<IService>().To<Service>();
var client = new Client(kernel.Get<Service>());
}
The example above demonstrates the usage of Ninject but it's far away from impressing, isn't it? Let us see Ninject performing something more clever:
using (IKernel kernel = new StandardKernel())
{
kernel.Bind<IService>().To<Service>();
var client = kernel.Get<Client>();
}
Did you see some kind of ninja stuff happened here? Ninject is able to build a Client and taking care of providing the dependencies.
Since this post is about IOC in general and not providing detailed information about Ninject, let us start only a short survey to the more complex ninja abilities.
Contextual binding:
// only if we're injecting in to the Client
kernel.Bind<IService>().To<Service>().WhenInjectedInto<Client>();
// assuming we have a a Settings object, providing a boolean property "OnlineMode" and two types implementing the IService interface
kernel.Bind<IService>().To<OnlineService>().When(request => kernel.Get<Settings>().OnlineMode);
kernel.Bind<IService>().To<LocalService>().When(request => !kernel.Get<Settings>().OnlineMode);
Controlling the life cycle of the objects:
Since the kernel provides us the objects of the types we are requesting, it is also easily able to take control of the different lifecycle-scopes the objects are in. Ninjects provides the following 4 scopes:
- Transient (default)
- Singleton (only one instance)
- Thread (one instance per thread)
- Request (one instance per web request)
The following example demonstrates how requesting objects in singleton scope works:
kernel.Bind<IService>().To<Service>().InSingletonScope();
var client1 = kernel.Get<Client>();
var client2 = kernel.Get<Client>();
Assert.Same(client1, client2);
Please note that this is only a short summary of what IOC and especially Ninject can do for you. If you are interested in becoming an IOC-Ninja, I suggest you to read the Ninject Walkthrough on CodePlex. If you are new to dependency injection at all, my recommendation is to start with Manual Dependency Injection as described at the beginning of the post.