.NET Zone is brought to you in partnership with:

A software engineer from Italy, Stefano Ricciardi has started his career developing mobile applications for Motorola, and has now moved to enterprise applications development. He's currently busy developing libraries on .NET manipulating large amounts of data, mostly related to time series observations and their statistical analysis. In his spare time, he loves running and photography. Stefano is a DZone MVB and is not an employee of DZone and has posted 7 posts at DZone. You can read more from them at their website. View Full User Profile

Ninject Mini Tutorial – Part 2

08.01.2012
| 3900 views |
  • submit to reddit

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, it’s 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

Published at DZone with permission of Stefano Ricciardi, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)