6 min read

(For more resources related to this topic, see here.)

Dependencies can be injected in a consumer class using different patterns and injecting them into a constructor is just one of them. While there are some patterns that can be followed for injecting dependencies, there are also some patterns that are recommended to be avoided, as they usually lead to undesirable results. In this article, we will examine only those patterns and antipatterns that are somehow relevant to Ninject features.

Constructor Injection

Constructor Injection is the most common and recommended pattern for injecting dependencies in a class. Generally this pattern should always be used as the primary injection pattern unless we have to use other ones. In this pattern, a list of all class dependencies should be introduced in the constructor.

The question is what if the class has more than one constructor. Although Ninject’s strategy for selecting constructor is customizable, its default behavior is selecting the constructor with more parameters, provided all of them are resolvable by Ninject. So, although in the following code the second constructor introduces more parameters, Ninject will select the first one if it cannot resolve IService2 and it will even use the default constructor if IService1 is not registered either. But if both dependencies are registered and resolvable, Ninject will select the second constructor because it has more parameters:

public class Consumer { private readonly IService1 dependency1; private readonly IService2 dependency2; public Consumer(IService1 dependency1) { this.dependency1 = dependency1; } public Consumer(IService1 dependency1, IService2 dependency2) { this.dependency1 = dependency1; this.dependency2 = dependency2; } }

If the preceding class had another constructor with two resolvable parameters, Ninject would throw an ActivationException exception notifying that several constructors had the same priority.

There are two approaches to override this default behavior and explicitly select a constructor. The first approach is to indicate the desired constructor in a binding as follows:

Bind<Consumer>().ToConstructor(arg => new Consumer(arg.Inject<IService1>()));

In the preceding example, we explicitly selected the first constructor. Using the Inject<T> method that the arg argument provides, we requested Ninject to resolve IService1 in order to be injected into the specified constructor.

The second method is to indicate the desired constructor using the [Inject] attribute:

[Inject] public Consumer(IService1 dependency1) { this.dependency1 = dependency1; }

In the preceding example, we applied the Ninject’s [Inject] attribute on the first constructor to explicitly specify that we need to initialize the class by injecting dependencies into this constructor; even though the second constructor has more parameters and the default strategy of Ninject would be to select the second one. Note that applying this attribute on more than one constructor will result in the ActivationException.

Ninject is highly customizable and it is even possible to substitute the default [Inject] attribute with another one, so that we don’t need to add reference to the Ninject library from our consumer classes just because of an attribute:

kernel.Settings.Set("InjectAttribute",typeof(MyAttribute));

Initializer methods and properties

Apart from constructor injection, Ninject supports the injection of dependencies using initializer methods and property setters. We can specify as many methods and properties as required using the [Inject] attribute to inject dependencies. Although the dependencies will be injected to them as soon as the class is constructed, it is not possible to predict in which order they will receive their dependencies. The following code shows how to specify a property for injection:

[Inject]
public IService Service
{
get { return dependency; }
set { dependency = value; }
}

Here is an example of injecting dependencies using an injector method:

[Inject]
public void Setup(IService dependency)
{
this.dependency = dependency;
}

Note that only public members and constructors will be injected and even the internals will be ignored unless Ninject is configured to inject nonpublic members.

In Constructor Injection, the constructor is a single point where we can consume all of the dependencies as soon as the class is activated. But when we use initializer methods the dependencies will be injected via multiple points in an unpredictable order, so we cannot decide in which method all of the dependencies will be ready to consume. In order to solve this problem, Ninject offers the IInitializable interface. This interface has an Initialize method which will be called once all of the dependencies have been injected:

public class Consumer:IInitializable
{
private IService1 dependency1;
private IService2 dependency2;
[Inject]
public IService Service1
{
get { return dependency1; }
set { dependency1 = value; }
}
[Inject]
public IService Service2
{
get { return dependency2; }
set { dependency2 = value; }
}
public void Initialize()
{
// Consume all dependencies here
}
}

Although Ninject supports injection using properties and methods, Constructor Injection should be the superior approach. First of all, Constructor Injection makes the class more reusable, because a list of all class dependencies are visible, while in the initializer property or method the user of the class should investigate all of the class members or go through the class documentations (if any), to discover its dependencies.

Initialization of the class is easier while using Constructor Injection because all the dependencies get injected at the same time and we can easily consume them at the same place where the constructor initializes the class. As we have seen in the preceding examples the only case where the backing fields could be readonly was in the Constructor Injection scenario. As the readonly fields are initializable only in the constructor, we need to make them writable to be able to use initializer methods and properties. This can lead to potential mutation of backing fields.

Service Locator

Service Locator is a design pattern introduced by Martin Fowler regarding which there have been some controversies. Although it can be useful in particular circumstances, it is generally considered as an antipattern and preferably should be avoided. Ninject can easily be misused as a Service Locator if we are not familiar to this pattern. The following example demonstrates misusing the Ninject kernel as a Service Locator rather than a DI container:

public class Consumer
{
public void Consume()
{
var kernel = new StandardKernel();
var depenency1 = kernel.Get<IService1>();
var depenency2 = kernel.Get<IService2>();
...
}
}

There are two significant downsides with the preceding code. The first one is that although we are using a DI container, we are not at all implementing DI. The class is tied to the Ninject kernel while it is not really a dependency of this class. This class and all of its prospective consumers will always have to drag their unnecessary dependency on the kernel object and Ninject library. On the other hand, the real dependencies of class (IService1 and IService2) are invisible from the consumers, and this reduces its reusability. Even if we change the design of this class to the following one, the problems still exist:

public class Consumer
{
private readonly IKernel kernel;
public Consumer(IKernel kernel)
{
this.kernel = kernel;
}
public void Consume()
{
var depenency1 = kernel.Get<IService1>();
var depenency2 = kernel.Get<IService2>();
...
}
}

The preceding class still depends on the Ninject library while it doesn’t have to and its actual dependencies are still invisible to its consumers. It can easily be refactored using the Constructor Injection pattern:

public Consumer(IService1 dependency1, IService2 dependency2)
{
this.dependency1 = dependency1;
this.dependency2 = dependency2;
}

Summary

In this article we studied the most common DI patterns and anti-patterns related to Ninject.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here