Welcome Guest Donate | Search | Active Topics | Members | Log In | Register

Dependency Injection Framework: Unity Application Block and ObjectBuilder Options
lingy
Posted: Wednesday, November 25, 2009 1:24:42 PM

Rank: Administration
Groups: Administration , Member

Joined: 5/8/2009
Posts: 1,576
Points: 5,631

The Unity Application Block (Unity) is a lightweight extensible dependency injection container with support for constructor, property, and method call injection. It facilitates building loosely coupled applications and provides developers with the following advantages:

  • Simplified object creation, especially for hierarchical object structures and dependencies
  • Abstraction of requirements; this allows developers to specify dependencies at run time or in configuration and simplify management of crosscutting concerns
  • Increased flexibility by deferring component configuration to the container
  • Service location capability; this allows clients to store or cache the container
  • Instance and type interception

Dependency injection techniques used in the Unity Application Block (Unity) provide ways to handle the dependencies between objects. For example, an object that processes customer information may depend on other objects that access the data store, validate the information, and check that the user is authorized to perform updates. It can ensure that the customer class correctly instantiates and populates all of these objects, especially where the dependencies may be abstract.

The posting in this topic explains what is Unity Application Block and how to use it in your application.

Sponsor
Posted: Wednesday, November 25, 2009 1:24:42 PM


lingy
Posted: Wednesday, November 25, 2009 2:44:43 PM

Rank: Administration
Groups: Administration , Member

Joined: 5/8/2009
Posts: 1,576
Points: 5,631

Background Knowledge

Before you dig into Unity Application Block (Unity), you should have read some backgroud knowledge about Inversion of Control (IoC) and Dependency Injection (DI).

lingy
Posted: Thursday, November 26, 2009 11:46:59 AM

Rank: Administration
Groups: Administration , Member

Joined: 5/8/2009
Posts: 1,576
Points: 5,631

As you know, we always define an interface to abastract a service provided by the different concrete classes. In the following example, we define an interface named IInvoice to present the general invoice services proivded by the invoices A and B. To simplify the example, we just declare the Title property in the interface:

public interface IInvoice
{
    string Title
    {
        get;
    }
}

class InvoiceA : IInvoice
{
    public InvoiceA()
    {
    }
    public string Title
    {
        get
        {
            return "Invoice A";
        }
    }
}

class InvoiceB : IInvoice
{
    public InvoiceB()
    {
    }
    public string Title
    {
        get
        {
            return "Invoice B";
        }
    }
}

We also define an InvoiceManager class to represent all kinds of invoice operation, such as issue an invoice.

class InvoiceManager
{
    private IInvoice _invoice;

    public InvoiceManager(IInvoice invoice)
    {
        _invoice = invoice;

    }
    public void IssueInvoice()
    {
        Console.WriteLine(this._invoice.Title);
    }
}

Please note that we are using the IInvoice interface instead of concrete class in the above code to facilitate operate different voices uniformly. For example, if you want to issue an invoice A, you can do it like:

InvoiceManager invoiceMgr = new InvoiceManager(new InvoiceA());

invoiceMgr.IssueInvoice();

Next time, if you want to issue an invoice B, you can do it:

InvoiceManager invoiceMgr = new InvoiceManager(new InvoiceB());

invoiceMgr.IssueInvoice();

The following figure illustrate the dependecies among the three entities.

The InvoiceManager class is dependent on the IInvoice interface and upon both the implementations, that means the InvoideManager has to know what invoice to issue at compile time.

We would prefer it if it were only dependent on the interface shown in the following figure, but then how do we make an instance to work with?

lingy
Posted: Thursday, November 26, 2009 1:38:38 PM

Rank: Administration
Groups: Administration , Member

Joined: 5/8/2009
Posts: 1,576
Points: 5,631

To decouple the direct dependency bwtween the InvoiceManager and the instance of IInvoice, we need some mechanism to create the instance of IInoice for InvoiceManger when it is needed. One way to do it is using Dependency Inject (DI), which allows you to inject invoice instaance into the InvoiceManager class, rather than relying on the InvoiceManager class to create the Invoice instance itself.

Unity Application Block implements the Dependenyc Inject mechanism using Containers. Dependency Inject Containers provide a layer of abstraction in which to house components and can be used to reduce the kind of dependency coupling described above by providing generic factory classes that instantiate instances of classes. These instances are then configured by the container, allowing construction logic to be reused on a broader level.

 

lingy
Posted: Thursday, November 26, 2009 2:00:51 PM

Rank: Administration
Groups: Administration , Member

Joined: 5/8/2009
Posts: 1,576
Points: 5,631

Let's rewrite code to demonstrate how to use the Unity Container mechanism:

1. Create a new console project called DIConsoleDemo

2. Add references to the following assemblies:

  • System.Configuration
  • Microsoft.Practices.Unity
  • Microsoft.Practices.Unity.Configuration

3. Create 4 types

namespace DIConsoleDemo
{
    public interface IInvoice
    {
        string Title
        {
            get;
        }
    }
}

namespace DIConsoleDemo
{
    class InvoiceA : IInvoice
    {
        public InvoiceA()
        {
        }
        public string Title
        {
            get
            {
                return "Invoice A";
            }
        }
    }
}

namespace DIConsoleDemo
{
    class InvoiceB : IInvoice
    {
        public InvoiceB()
        {
        }
        public string Title
        {
            get
            {
                return "Invoice B";
            }
        }
    }
}


namespace DIConsoleDemo
{
    class InvoiceManager
    {
        private IInvoice _invoice;
        public InvoiceManager(IInvoice invoice)
        {

            _invoice = invoice;
        }

        public void IssueInvoice()
        {
            Console.WriteLine(this._invoice.Title);
        }

    }
}

4. Create the application configuration file (App.config):

<?xml version="1.0" encoding="utf-8" ?>

<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
  </configSections>
  <unity>
    <typeAliases>
      <typeAlias alias="IInvoice" type="DIConsoleDemo.IInvoice, DIConsoleDemo" />
      <typeAlias alias="InvoiceA" type="DIConsoleDemo.InvoiceA, DIConsoleDemo" />
      <typeAlias alias="InvoiceB" type="DIConsoleDemo.InvoiceB, DIConsoleDemo" />
    </typeAliases>
    <containers>
      <container>
        <types>
          <type type ="IInvoice" mapTo="InvoiceA">
          </type>
        </types>
      </container>
    </containers>
  </unity>
</configuration>

5. In the main() method, add the following code:

using System.Configuration;

using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace DIConsoleDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            IUnityContainer container = new UnityContainer();

            UnityConfigurationSection section =(UnityConfigurationSection)ConfigurationManager.GetSection("unity");

            section.Containers.Default.Configure(container);

            InvoiceManager invoiceMgr = container.Resolve<InvoiceManager>();

            invoiceMgr.IssueInvoice();
        }
    }
}

6. Run the proejct and see the result:

Invoice A

7. If you change the type mapping in the configuration file:

<type type ="IInvoice" mapTo="InvoiceB">

and run the proejct again, you will see the result:

Invoice B

That's it. To decouple the dependency, whenever you want an object, you have to use the container to create the instance using container.Resolve<T>() method instead of the "new" keyword. Although the InvoiceManager class is not configured in the App.config file, it is still participating in Dependency Injection. When asked to resolve the type InvoiceManager, the UnityContainer did not find any mapping to use to substitute InvoiceManager by something else. However, the UnityContainer analyzed the constructor of InvoiceManager and found that InvoiceManager has a dependency on IInvoice. So the UnityContainer resolved IInvoice into an InvoiceA based the mapping and then passed it to the InvoiceManager constructor.

lingy
Posted: Friday, November 27, 2009 10:55:17 AM

Rank: Administration
Groups: Administration , Member

Joined: 5/8/2009
Posts: 1,576
Points: 5,631

Forms of Dependency Injection

Untiy supports three main styles of dependency injection: Constructor Injection, Property(Setter) Injection and Method Call Injection. The following examples demonstrate how dependencies can be injected using these injections.

Constructor Injection

We have already seen dependency injection through the constructor in the above code, but there is one important note about this: If your class has multiple constructors, your must annotate the constructor you want UnityContainer to use by adding the [InjectionConstructor] attribute on it.

Property(Setter) Injection

Add these 3 classes to your project:

namespace DIConsoleDemo
{
    public interface ICustomer
    {
        string Name
        {
            get;
        }
    }
}

namespace DIConsoleDemo
{
    public class CustomerA : ICustomer
    {
        public string Name
        {
            get
            {
                return "Custoemr A";
            }
        }
    }
}
namespace DIConsoleDemo
{
    public class CustomerB : ICustomer
    {
        public string Name
        {
            get
            {
                return "Custoemr B";
            }
        }
    }
}
Modify the configuartion file as the following:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
  </configSections>
  <unity>
    <typeAliases>
      <typeAlias alias="IInvoice" type="DIConsoleDemo.IInvoice, DIConsoleDemo" />
      <typeAlias alias="InvoiceA" type="DIConsoleDemo.InvoiceA, DIConsoleDemo" />
      <typeAlias alias="InvoiceB" type="DIConsoleDemo.InvoiceB, DIConsoleDemo" />
     
<typeAlias alias="ICustomer" type="DIConsoleDemo.ICustomer, DIConsoleDemo" />
      <typeAlias alias="CustomerA" type="DIConsoleDemo.CustomerA, DIConsoleDemo" />
      <typeAlias alias="CustomerB" type="DIConsoleDemo.CustomerB, DIConsoleDemo" />

    </typeAliases>
    <containers>
      <container>
        <types>
          <type type ="IInvoice" mapTo="InvoiceA" />
          <type type ="ICustomer" mapTo="CustomerA" />
        </types>
      </container>
    </containers>
  </unity>
</configuration>

Modify the InvoiceManager by adding a new Propertyof the Customer and logic for IssueInvoice method:

using Microsoft.Practices.Unity;

namespace DIConsoleDemo
{
    class InvoiceManager
    {
        private IInvoice _invoice;
        private ICustomer _customer;

        public InvoiceManager(IInvoice invoice)
        {
            _invoice = invoice;

        }
        [Dependency]
        public ICustomer Customer
        {
            get
            {
                return this._customer;
            }
            set
            {
                this._customer = value;
            }
        }

        public void IssueInvoice()
        {
            Console.WriteLine(this._customer.Name);
            Console.WriteLine(this._invoice.Title);
        }
    }
}

Notice the use of the [Dependency] attribute. This will indicate to the UnityContainer that a dependency injection is required here.

Build and run the project, you will see the result:

Customer A

Invoice A

The program did resolve ICustomer and injected a CustomerA instance into the InvoiceManager.Customer property.

Method Call Injection

You can do pretty much the same thing using a method call instead of a property.

Replace the Customer the property by a SetCustomer() method:

using Microsoft.Practices.Unity;

namespace DIConsoleDemo
{
    class InvoiceManager
    {
        private IInvoice _invoice;
        private ICustomer _customer;

        public InvoiceManager(IInvoice invoice)
        {
            _invoice = invoice;

        }
        [InjectionMethod]
        public void SetCustomer(ICustomer customer)
        {
            this._customer = customer;
        }

        public void IssueInvoice()
        {
            Console.WriteLine(this._customer.Name);
            Console.WriteLine(this._invoice.Title);
        }
    }
}

In the case of a method, use the [InjectionMethod] attribute to indicate to UnityContainer that a dependency injection is required.

Build and run the project. The program did resolve ICustomer and injected a CustomerA instance by calling the InvoiceManager.SetCustomer method.

lingy
Posted: Friday, November 27, 2009 2:45:26 PM

Rank: Administration
Groups: Administration , Member

Joined: 5/8/2009
Posts: 1,576
Points: 5,631

Lifetime management in Unity

The UnityContainer is able to manage the lifetime of the objects it resolves. By default, the UnityContainer creates a new instance of any type it resolves. You can prove this with this unit test:

There are 4 basic options for lifetime management:

  1. No management: The container does not keep a reference on the objects it creates and therefore creates a new object each time Resolve<T>() is called.
  2. Singleton: The container always returns the same instance
  3. Singleton-per-thread: The container keeps one instance for each thread and return the instance based on the current thread.
  4. Externally controlled: You provide a class that will manage the instances. This option is not covered in this article.

The configuration of lifetime management is done at the <type> level. Each lifetime management option is supported by one type (class) in the unity application block library.

For example, the class ContainerControlledLifetimeManager is the class that implements the "singleton" lifetime management style.

- To resolve the IInvoice type using a singleton, change the <type> section this way:

<type type ="IInvoice" mapTo="InvoiceA">

    <lifetime type= "Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity" />

</type>

Using the fully qualified name for the ContainerControlledLifetimeManager class can make files hard to read. The configuration section for unity supports defining aliases. The aliases can then be used anywhere a type name is expected. I recommend defining 3 aliases for the 3 types of lifetime management option.

Inside the <unity> section (but above and outside the <containers> sections, add the following alias definitions:

<typeAliases>

    <typeAlias alias="singleton"

        type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity" />

    <typeAlias alias="perThread"

        type="Microsoft.Practices.Unity.PerThreadLifetimeManager, Microsoft.Practices.Unity" />

    <typeAlias alias="external"

        type="Microsoft.Practices.Unity.ExternallyControlledLifetimeManager, Microsoft.Practices.Unity" />

</typeAliases>

- You can now simplify the <type> declaration for IInvoice this way:

<type type ="IInvoice" mapTo="InvoiceA">
 <lifetime type="singleton" />
</type>

If you delcare singleton for IInvoice, then the above will not throw any exception anymore.

You can also specify a lifetime for type that does not have a mapping. In this case, add a <type> section for that type but do not add a mapTo attribute. Then add the <lifetime> element to that type.

Users browsing this topic
Guest


Forum Jump
You cannot post new topics in this forum.
You cannot reply to topics in this forum.
You cannot delete your posts in this forum.
You cannot edit your posts in this forum.
You cannot create polls in this forum.
You cannot vote in polls in this forum.


© 2010 Canaware Solutions. All rights reserved.
Powered by Canaware Forum version 2.4