Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Injection Patterns

SMoni edited this page · 28 revisions

Ninject supports three styles of injection out of the box, which are listed here. Each form of injection has its own benefits and detriments, and you may find each to be useful in different circumstances. The way in which the styles work and the trade-offs involved in deciding which is best in a given situation is similar to the decision making process involved when using other dependency injection containers. These considerations can be subtle in nature. While this article discusses some aspects of the tradeoffs, this section should definitely not be considered the last work on DI-based architecture.

Constructor Injection

The primary DI pattern is Constructor Injection. When activating an instance of a type Ninject will choose one of the type’s constructors to use by applying the following rules in order:-

  1. If a constructor has an [Inject] attribute, it is used (but if you apply the attribute to more than one, Ninject will throw a NotSupportedException at runtime upon detection).
  2. If no constructors have an [Inject] attribute, Ninject will select the one with the most parameters that Ninject understands how to resolve.
  3. If no constructors are defined, Ninject will select the default parameterless constructor (assuming there is one).
class Samurai 
{
    readonly IWeapon weapon;
  
    public Samurai(IWeapon weapon)
    {
        if(weapon == null)
            throw new ArgumentNullException("weapon");
        this.weapon = weapon;
    }
  
    public void Attack(string target)
    {
        this.weapon.Hit(target);
    }
}

The implementation style above offers the following benefits:

  1. the full set of dependencies are visible at a glance in a single place
  2. there is no confusion as to when they become available
  3. all validation can be completed in a single DRY place
  4. you can lean on the Compiler by using readonly to confirm that there is no mutation of the backing fields
  5. your code isn’t constantly having to reference or talk about a specific DI container (i.e., you’re not using attributes that tie you to a container)
  6. if you’re writing tests, or using the types in contexts where it’s getting instantiated outside of the context of your DI container, the compiler will be there making it clear what the class relies on (whereas it’s easy to miss a [Inject] annotation in a base class

It is important to note that the [Inject] attribute can be left off completely. This means that the majority of your code doesn’t need to be aware of Ninject and won’t need to reference the Ninject namespace/libraries. This is also critical if you don’t have access to the source code of a class but you still want to inject dependencies into it. (Also one can tell Ninject to look for any custom marker attribute of your choice i.e., something other than [Inject] by having the Kernel be passed a NinjectSettings with a different InjectAttribute. There’s also the capability to customize the ConstructorSelectonStrategy internally)

Initialization methods

In cases where Constructor Injection cannot meet your needs, Ninject offers two techniques for having dependencies supplied by the container after construction has taken place (either after Ninject has constructed the object or as part of a Kernel.Inject call). While they superficially appear quite different, the mechanism and semantics is common:

  1. After construction has taken place, methods marked with [Inject] and the setters of properties marked with [Inject] get called with freshly resolved instances of each dependency
  2. The order of calling is not deterministic; there is no way to tell in which order Ninject will inject the values. (However, you can also request notification after all injections are complete by implementing either the the IInitializable or the IStartable interface, discussed later.)

The key weaknesses when compared to constructor injection are:

  1. you cannot use readonly to Lean on the Compiler to guarantee things are correctly initialized (and centralize your validation in a single place)
  2. Analyzing the different places from which Weapon may be set is a whole lot more complex than tracing back from a single constructor
  3. you can no longer see at a glance what the type depends on
  4. it requires the [Inject] Attribute Type declaration to be visible at the point of use. This ties your code to a specific container. (Although Ninject does permits the customization of the specific attribute to look for, the point remains – you’re polluting an interface with external concerns.)
  5. In order to correctly protect the invariants of a class, it is now necessary to consider, implement and maintain validation logic handling the fact that anyone outside can call any of your Initialization Methods at any time in any order.

Setter Method Injection

Here’s an example of the same Samurai class using Setter Method Injection. For a method to be called after construction of the object, it must be annotated with [Inject].

class Samurai
{
    IWeapon weapon;
  
    [Inject]
    public void Arm(IWeapon weapon)
    {
        this.weapon = weapon;
    }
  
    public void Attack(string target) 
    {
        this.weapon.Hit(target);
    }
}

One can tag multiple methods across a class hierarchy (TODO: Fact check) and each will be called during the composition of the object graph, there is no guarantee regarding what order they will be invoked.

Setter Method Injection should not be confused with Method Injection as discussed in Mark Seemann’s Dependency Injection in .NET book which is a completely different pattern which Ninject does not address directly.

Property Setter Injection

Here’s an example of the same Samurai class using Property Setter Injection. For a property to be injected it must be annotated with [Inject].

class Samurai 
{    
    [Inject]
    public IWeapon Weapon { private get; set; }
  
    public void Attack(string target) 
    {
        this.Weapon.Hit(target);
    }
}

Setter Method Injection is really just a special case of Property Setter Injection without the implicit set_PropertyName method name under the hood and thus shares most of the same disadvantages. While it may seem on the surface to be a much neater way of implementing dependency injection than Constructor Injection, there are a number of weaknesses in this approach as mentioned in Initialization methods above which should be carefully considered before opting to use them.

Additionally, you’re left with one of the following weaknesses in your class interface:

  1. a write-only property (as used above) is generally considered bad practice
  2. making the getter visible means exposing an externally managed dependency via your public interface, which is a worse idea

Property Setter Injection as discussed here should not be confused with the Property Injection Pattern as described in Mark Seemann’s Dependency Injection in .NET book which refers to the management of optional dependencies which is implemented in some other containers – allowing one to have properties be auto-initialized or not depending on the configuration of your container. This mechanism induces even more fragility and lack of predictability and is not implemented in Ninject. Should one wish to brute-force such an approach, introducing a customization into the container similar to how the triggering of IInitializable or the IStartable is managed may be a viable approach but is definitely not in the realm of established common practice.

Injecting Fields

Field Injection is a bad practice, and got cut for minimization as part of the rewrite between v1 and v2. There is no field injection in Ninject v2.

Managing Constructor Over-Injection

A key trade-off when contrasting Property Setter Injection with Constructor Injection is that adding a dependency to a constructor can have knock-on effects in terms of requiring you to create lots of constructors that just pass lots of parameters through to :base(....). While dropping down to Property Setter Injection to save lots of typing may seem like a no-brainer, this is often a code smell which can be better remedied by e.g.,

Continue reading: Multi Injection

Something went wrong with that request. Please try again.