Ninject Mini Tutorial - Part 2

2 minute read

Go to Part 1

Controlling the Life Cycle of your Objects

In the previous post we did not concern ourselves with the lifecycle of the object returned from Ninject kernel. Ninject provides the following 4 built-in lifecycles (scopes):

  1. Transient (default)
  2. Singleton (only one instance)
  3. Thread (one instance per thread)
  4. Request (one instance per web request).

You can create custom scopes if needed.

Singleton:

using (IKernel kernel = new StandardKernel())
{
    kernel.Bind<ITaxCalculator>()
        .To<TaxCalculator>()
        .InSingletonScope()
        .WithConstructorArgument("rate", .2M);

    var tc1 = kernel.Get<ITaxCalculator>();
    var tc2 = kernel.Get<ITaxCalculator>();

    Assert.Same(tc1, tc2);
}

Transient:

using (IKernel kernel = new StandardKernel())
{
    kernel.Bind<ITaxCalculator>()
        .To<TaxCalculator>()
        .InTransientScope() 
        .WithConstructorArgument("rate", .2M);

        var tc1 = kernel.Get<ITaxCalculator>();
        var tc2 = kernel.Get<ITaxCalculator>();

        Assert.NotSame(tc1, tc2);
}

More Details on Injection Patterns

With Ninject you can inject:

  1. Constructor parameters
  2. Properties
  3. Methods

Before considering each one in turn, we just need to introduce the [Inject] attribute which may be used to tag constructors, properties and methods requiring injection. Obviously, by tagging constructors, properties or methods, your objects cease to be POCOs.

Constructor Injection

We have already seen an example of constructor injection in Part I when the kernel auto-magically injected an implementation of ITaxCalculator to the Sale Area constructor. In that case, even if we didn't tag the constructor with the [Inject] attribute, the kernel was able to perform the required binding. How?

That was actually a special case: when there is only one constructor available, the tagging is not needed. On the other hand, if there's more than one constructor defined, then then kernel can inject a dependency to only one constructor that needs to have the [Inject] attribute:

public class Sale3
{
    private readonly ITaxCalculator taxCalculator;

    public Sale3() { }

    [Inject]
    public Sale3(ITaxCalculator taxCalculator)
    {
        this.taxCalculator = taxCalculator;
    }

    // other stuff

}

Properties Injection

Instead of passing in the dependencies through the constructor, you can also inject them as properties. Injecting properties is pretty straightforward:

public class Sale2 
{ 
    [Inject] 
    public ITaxCalculator TaxCalculator { get; set; } 
    
    // implicit default constructor and other stuff... 


    public decimal GetTotal() 
    { 
        decimal total = 0M; 
        foreach (var item in lineItems) 
        { 
            total += TaxCalculator.CalculateTax(item.TotalPrice) 
                    + item.TotalPrice; 
        } 
        
        return total; 
    } 
}

Usage (note that we never explicitely set the TaxCalculator):

using (IKernel kernel = new StandardKernel())
{
    kernel.Bind<ITaxCalculator>()
                  .To<TaxcCalculator>()
                  .WithConstructorArgument("rate", .2M);

    var lineItem1 = new SaleLineItem("Gone with the wind", 10M, 1);
    var lineItem2 = new SaleLineItem("Casablanca", 5M, 2);

    var sale = kernel.Get<Sale2>(); // property injection!

    sale.AddItem(lineItem1);
    sale.AddItem(lineItem2);

    Assert.Equal(24M, sale.GetTotal());
}

There's an important caveat: if you have 2 or more properties injected, the order in which each dependency is injected is not predictable. This might complicate your design, if those dependencies are coupled somehow (e.g. dependency A needs dependency B). For this kind of situations, constructor or method injection is usually preferred.

Methods Injection

Finally, it's also possible to tag methods for injection. As with constructor parameters, its possible to inject more than one value at once.

public class Sale4
{
    private ITaxCalculator taxCalculator;

    // other stuff


    // method injection, will be called by the kernel

    [Inject]
    public void SetTaxCalculator(ITaxCalculator taxCalculator)
    {
        this.taxCalculator = taxCalculator;
    }

    public decimal GetTotal()
    {
        decimal total = 0M;
        foreach (var item in lineItems)
        {
            total += taxCalculator.CalculateTax(item.TotalPrice) 
                  + item.TotalPrice;
        }

        return total;
    }
}

Usage (note that we never explicitely call the SetTaxCalculator):

using (IKernel kernel = new StandardKernel())
{
    kernel.Bind<ITaxCalculator>()
          .To<TaxCalculator>()
          .WithConstructorArgument("rate", .2M);

    var lineItem1 = new SaleLineItem("Gone with the wind", 10M, 1);
    var lineItem2 = new SaleLineItem("Casablanca", 5M, 2);

    var sale = kernel.Get<Sale4>(); // method injection!

    sale.AddItem(lineItem1);
    sale.AddItem(lineItem2);

    Assert.Equal(24M, sale.GetTotal());
}

Go to Part 1

Updated:

Leave a Comment