This article explains how to get started with the Unity Application Block using a simple console application. Throughout this tutorial you will learn how to configure and use the "Inversion of Control" and "Dependency Injection" features of the Unity Application Block.
Assumptions
This article assumes that:
You know what DIP, IoC and DI are.
You want to configure your type mappings using a configuration file; not using code.
Container
The "Container" or "Inversion of Control Container" is the main object that is used to create objects and inject dependencies into them. Whenever you want an object to be open to IoC, you have to use the container to create the instance using container.Resolve<T>() method instead of the "new" keyword.
IService service = unityContainer.Resolve< IService>();
Create the project and add references to the required assemblies
- Create a new console project called UnityTest (if you use another name, you will have to change type names in configuration files so I recommend you stick to this name).
- Add references to the following assemblies:
- System.Configuration
- Microsoft.Practices.ObjectBuilder2
- Microsoft.Practices.Unity
- Microsoft.Practices.Unity.Configuration
Create the application configuration file
- Add App.config configuration file to your project
- Enter the following code in the App.config file
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section
name="unity"
type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity>
<containers>
<container>
<types>
</types>
</container>
</containers>
</unity>
</configuration>
This is the smallest working configuration file you can have for Unity to work. There is one configSections element that describes how .Net should read the upcoming section.
There is one, empty, container configuration nested in a containers, nested in a sections called "unity". With this configuration, your will be able to create and configure the container object and then use the container to create objects.
Create and configure the UnityContainer instance
In your project's Main() method, add the following code to create a container instance and configure it by reading the "unity" configuration section defined in the app.config file.
static void Main(string[] args)
{
IUnityContainer container = new UnityContainer();
UnityConfigurationSection section =
(UnityConfigurationSection)ConfigurationManager.GetSection("unity");
section.Containers.Default.Configure(container);
}
That's it. You can now create instances using container.Resolve<T>() for any type. As you will see, the container is able to Resolve any type, even if no type mapping exist in the configuration file. If you give it a concrete type, it will simply create an instance of that type and return it to you. You can later add a type mapping for that type and the real IoC benefits will be introduced automatically.
Create an instance and inject dependencies
Add these 3 types to your project.
public class InvoiceManager
{
private IInvoicingService _invoicingService;
public InvoiceManager(IInvoicingService invoicingService)
{
_invoicingService = invoicingService;
}
public void Manage()
{
Console.WriteLine(_invoicingService.GetCount());
}
}
public interface IInvoicingService
{
string GetCount();
}
public class InvoicingService : IInvoicingService
{
public InvoicingService()
{
}
public string GetCount()
{
return DateTime.Now.ToString();
}
}
Take a minute to study the dependencies: An InvoiceManager uses (depends on) an IInvoiceService. The class InvoicingService it the concrete implementation for IInvoiceService.
Add a type mapping in the App.config file. Change the content of the <unity> section this way:
<containers>
<container>
<types>
<type type ="UnityTest.IInvoicingService, UnityTest"
mapTo="UnityTest.InvoicingService, UnityTest">
</type>
</types>
</container>
</containers>
- Append these lines to your Main() function without changing the existing lines that create and configure the container.
IInvoicingService service = container.Resolve<IInvoicingService>();
console.WriteLine( service.GetCount());
- Run the project and see the result.
The container returns an InvoiceService when asked to resolve the type IInvoiceService.
- Delete the two new lines and replace them by the following two lines:
InvoiceManager manager = container.Resolve<InvoiceManager>();
manager.Manage();
- Run the project and see the result.
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 IInvoicingService. So the UnityContainer resolved IInvoicingService into an InvoiceService and then passed it to the InvoiceManager constructor.
If you are curious, add breakpoints to the InvoiceService and InvoiceManager constructors and run the program. Notice in which order objects are created. Have a look at the call stack windows just to have an idea of what is happening under the hood.
Lifetime management
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:
IInvoicingService service1 = container.Resolve<IInvoicingService>();
IInvoicingService service2 = container.Resolve<IInvoicingService>();
Assert.AreNotSame( service1, service2 );
There are 4 basic options for lifetime management:
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.
Singleton: The container always returns the same instance
Singleton-per-thread: The container keeps one instance for each thread and return the instance based on the current thread.
Externally controlled: You provide a class that will manage the instances. This option is not covered in this article.
Lifetime management configuration
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 IInvoicingService type using a singleton, change the <type> section this way:
<type type="UnityTest.IInvoicingService, UnityTest"
mapTo="UnityTest.InvoicingService, UnityTest">
<lifetime type= "Microsoft.Practices.Unity.ContainerControlledLifetimeManager,
Microsoft.Practices.Unity" />
</type>
Lifetime management using aliases
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 IInvoicingService this way:
<type type="UnityTest.IInvoicingService, UnityTest"
mapTo="UnityTest.InvoicingService, UnityTest">
<lifetime type="singleton" />
</type>
Tip: You can also define aliases the same way for your own types if you would like to simplify the type mapping for your own type.
Tip: 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.
Other methods for injecting dependencies
So far, we have seen that the UnityContainer can inject dependencies by analyzing the constructor of a type and passing the necessary dependencies to it. In this section, we will see how dependencies can be injected using other mechanisms.
Dependency injection by property assignment
- Add these two classes to your project:
public interface ICustomerService
{
string GetCustomer();
}
public class CustomerService : ICustomerService
{
public string GetCustomer()
{
return "Sylvain Hamel";
}
}
- Add the following type mapping into the configuration file:
<type type="UnityTest.ICustomerService, UnityTest"
mapTo="UnityTest.CustomerService, UnityTest">
<lifetime type="singleton" />
</type>
- Change the InvoiceManager by adding a new Property of type ICustomerService:
private ICustomerService _customerService;
[Dependency]
public ICustomerService CustomerService
{
get { return _customerService; }
set { _customerService = value; }
}
Notice the use of the [Dependency] attribute. This will indicate to the UnityContainer that a dependency injection is required here.
- Change the Manage() method of the InvoiceManager this way
public void Manage()
{
Console.WriteLine(_invoicingService.GetCount());
Console.WriteLine(_customerService.GetCustomer());
}
- Run the project.
As you probably expected, the program did resolve ICustomerService and injected a CustomerService instance into the InvoiceManager.CustomerService property.
Dependency injection by method call
You can do pretty much the same thing using a method call instead of a property.
- Replace the CustomerService the property by a SetCustomerService() method:
[InjectionMethod]
public void SetCustomerService(ICustomerService service)
{
_customerService = service;
}
In the case of a method, use the [InjectionMethod] attribute to indicate to UnityContainer that a dependency injection is required.
- Run the project.
And of course, the program did resolve ICustomerService and injected a CustomerService instance by calling the InvoiceManager.SetCustomerService method.
Note on dependency injection by constructor
We have already seen dependency injection through the constructor 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.
Conclusion
Hopefully this tutorial will you get started with the Unity Application Block.
very good article.. Started my first UA Program from here. Thanks a llot
ReplyDeleteFor a newbie like myself, I find this tutorial difficult to follow because:
ReplyDelete1) You didn't mention that one needs to download and install the Unity framework to get the required dlls.
2) Your code as it appears here, is difficult to read because the formatting indentation is bad (especially for the "Add these 3 types to your project.")
3) I cannot rely on simply cutting and pasting your code. For example, the code after "-Append these lines to your Main..." is "console.WriteLine( service.GetCount());". It requires a capital "C" on "console"!!!
It's the little things like this that prevents your tutorial from being an excellent source for a newbie. After finding and resolving these issues, I still cannot make this solution work. I get an error at runtime on the line 'UnityConfigurationSection section = (UnityConfigurationSection)...GetSection("unity");'. The error is:
"System.Configuration.ConfigurationErrorsException was unhandled
Message="Unrecognized attribute 'type'. Note that attribute names are case-sensitive. (C:\\Users\\msueping\\Documents\\Visual Studio 2008\\Projects\\UnityTest\\UnityTest\\bin\\Debug\\UnityTest.vshost.exe.Config line 13)"
Source="System.Configuration"
BareMessage="Unrecognized attribute 'type'. Note that attribute names are case-sensitive."
Filename="C:\\Users\\msueping\\Documents\\Visual Studio 2008\\Projects\\UnityTest\\UnityTest\\bin\\Debug\\UnityTest.vshost.exe.Config"
Line=13
StackTrace:
at System.Configuration.BaseConfigurationRecord.EvaluateOne(String[] keys, SectionInput input, Boolean isTrusted, FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentResult)
at System.Configuration.BaseConfigurationRecord.Evaluate(FactoryRecord factoryRecord, SectionRecord sectionRecord, Object parentResult, Boolean getLkg, Boolean getRuntimeObject, Object& result, Object& resultRuntimeObject)
at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
at System.Configuration.BaseConfigurationRecord.GetSectionRecursive(String configKey, Boolean getLkg, Boolean checkPermission, Boolean getRuntimeObject, Boolean requestIsHere, Object& result, Object& resultRuntimeObject)
at System.Configuration.BaseConfigurationRecord.GetSection(String configKey, Boolean getLkg, Boolean checkPermission)
at System.Configuration.BaseConfigurationRecord.GetSection(String configKey)
at System.Configuration.ClientConfigurationSystem.System.Configuration.Internal.IInternalConfigSystem.GetSection(String sectionName)
at System.Configuration.ConfigurationManager.GetSection(String sectionName)
at UnityTest.Program.Main(String[] args) in C:\Users\msueping\Documents\Visual Studio 2008\Projects\UnityTest\UnityTest\Program.cs:line 16
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
Where am I going wrong?
Thanks
Honestly, i love this post very much and is the best for the subject i looked at so far.
ReplyDeleteYes there is that "c" that should be in uppercase for "Console.WriteLine..." .
Otherwise, i copy pasted it and everything works smooth. I loved it and keep posting your great work!
Very nice Tutorial. It could not have been any simpler to understand the Unity Application Block
ReplyDeleteEasy to follow, I tried this and it was successful.
ReplyDeleteI came across this post after a week of searching for an easy to follow tutorial to get started with Unity. I found a lot of resources that started with a big project that required a day to understand what the app does and then try not to get confused with tons of irrelevant subjects covered. Thanks to this post, my search is over and now I can start other tutorials.
ReplyDeleteThis is THE intro tutorial to Unity. I also found the same problem that Millad mentioned, with all tutorials, except this one:
ReplyDeleteThey miss the point of teaching just the basic concept and get you confused with crap which is unrelated. This tutorial on the other hand shows Unity the way it will be used by most people, via an app.config file and the container.
The only problems I had were two:
1) ObjectBuilder2 does not exist anymore, but it is not needed.
2) The following line is deprecated:
section.Containers.Default.Configure(container);
Just needed to change to:
section.Configure(container);
All in all, bravo and thanks for the simplicity!
Fantastic Article with no NOISE distracting usage of Unity with other App defined crap.
ReplyDeletePerfect! One of the best article that I came across related to DI and Unity framework. Good work...
ReplyDeleteMuch appreciated and Thanks!
Good intro to Unity. I found the same issues as others, but 1) I suspected things have evolved since this article was written; and 2) Any developer can figure out stuff like starting Console with a capital "C"...ignore that Deutsche Bag!
ReplyDeleteVery good article.
ReplyDeleteMuch appreciated!
Thanks,
Girish Sakhare
Excellent, to the point intro! I do agree with the feedback about providing links to the downloads page, but I already did that and got super confused with some of the other "detailed" getting started blog posts. This one gives just enough info to get going.
ReplyDeletethanks
Great article. Simple intro to unity. Love to see more on this...
ReplyDeleteGood tutorial..
ReplyDelete____________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
ReplyDeleteGot to understand usage of Unity with clear example.
ReplyDeleteThanks.
Nice! Thanks!
ReplyDeleteThis is one of the best simple but able to give clear example. Demonstrated constructor, property and method injection. I was able to create my own console application and was able to run successfully. Thank you very much for posting this.
ReplyDeleteVery cool article, it make many points clear to me.
ReplyDeleteGood starting article for the library. Now (2016) one can use NUGET to obtain the Unity package and have it installed directly into the project.
ReplyDelete