Recap + Intro
In part 1 we looked at the problem with just adding logging code and how it quickly gets messy and we looked at how the more loosely coupled the code is, the easier it is to effect changes painlessly. Now we are going to take that “nicer” code (compared to the first version) and put Unity in the mix. Note you can get Unity with the Enterprise Library from p&p group.
As I said in part 1
what I am going to do is look at a practical approach to using Unity, I will not be going into DI or IoC those topics as people smarter than me have done a much better job. If you want the theory look somewhere else, if you want to get running with Unity this is for you.
This is a multi-part series as such here is the series guide in case you are looking for the rest of the series:
- Part 1 - Introduction to the problem
- Part 2 - Changing the code to use basic unity functions
- Part 3 - Life time management
- Part 4 - Changing the code to use interception
- Part 5 - Interception supplementary
- Part 6 - Wrap up
Applying Unity
The first step to using Unity is setting it up it, to do that you need to add three references to your solution (highlighted below):
Unity has two configuration options, one in code and one in an external configuration file (normally your app/web.config). They both have a use but for this series I will use the configuration file which means you also need to add a reference to:
I tend to find that the config file route is what I use in production while the code one is what I use in unit tests (normally to override config file settings).
Next add the references to your code:
using System.Configuration;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
Now in my main method of my application I am going to create an instance of an UnityContainer (line 21 below). This is effectively a magic bag, where we put our hand in and say what we want and pull it out - just like Mary Poppins had. Before we can do that we need to tell it to read the configuration information (lines 23 and 24 below) and then we retrieve the logger (line 26 below) from the magic bag. Note that we are NOT creating an instance of a logger on line 10 below as we did before, the act of pulling the logger out of the bag creates it.
1: using System;
2: using System.Configuration;
3: using Microsoft.Practices.Unity;
4: using Microsoft.Practices.Unity.Configuration;
5:
6: namespace BigSystem
7: {
8: class Program
9: {
10: static ILogger logger;
11:
12: static void DoSomething(string Username)
13: {
14: logger.LogThis("DoSomething Called with Username Parameter set to:" + Username);
15: Console.WriteLine("Hello {0}", Username);
16: logger.LogThis("DoSomething Completed");
17: }
18:
19: static void Main(string[] args)
20: {
21: IUnityContainer container = new UnityContainer();
22:
23: UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
24: section.Containers.Default.Configure(container);
25:
26: logger = container.Resolve<ILogger>();
27:
28: logger.LogThis("Application started");
29: DoSomething("Robert");
30: Console.ReadKey();
31: logger.LogThis("Application Completed");
32: }
33: }
34:
35: public interface ILogger
36: {
37: void LogThis(string Message);
38: }
39:
40: public class DebugLogger : ILogger
41: {
42: public void LogThis(string Message)
43: {
44: System.Diagnostics.Debug.WriteLine(String.Format("{0}: {1}", DateTime.Now, Message));
45: }
46: }
47:
48: public class ConsoleLogger : ILogger
49: {
50: public void LogThis(string Message)
51: {
52: Console.WriteLine("{0}: {1}", DateTime.Now, Message);
53: }
54: }
55: }
Lastly we need to dive into the app.config (add one now if you are following at home) and fight the tangled web of Unity configuration.
Unity Config
We start off by adding a configSection to tell the framework how to handle the new section for Unity (this is lines 3 to 5). Then on line 7 we open our unity tag and the first thing in there is the typeAliases. I recommend using type aliases as they allow you to centrally link a type (like BigSystem.ILogger) to a friendly name. In our example below it’s overkill we could’ve just used the types directly on line 16 (I’ll cover that in a second) but as your solution grows doing find and replace for types is a pain and having this alias system will make life easier. So lines 9 and 10 alias my class and interface to a friendly name.
Now line 13 is where the meat comes in, we open the containers tag which allows up to setup our container which is just a named group of settings. We have one container so we do not need to name it, and in there is the types tag (line 15) which allows us to map what interface to what class, so we we call container.Resolve<ILogger> (line 26 in the code above) it knows what class to return.
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <configSections>
4: <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
5: </configSections>
6:
7: <unity>
8: <typeAliases>
9: <typeAlias alias="Logger" type="BigSystem.DebugLogger, BigSystem"/>
10: <typeAlias alias="ILogger" type="BigSystem.ILogger, BigSystem"/>
11: </typeAliases>
12:
13: <containers>
14: <container>
15: <types>
16: <type type="ILogger" mapTo="Logger" />
17: </types>
18: </container>
19: </containers>
20: </unity>
21: </configuration>
So if you run the code it will actually do the logging to the Output window in Visual Studio!
Then if we change line 9 above as such it will output to the console!
<typeAlias alias="Logger" type="BigSystem.ConsoleLogger, BigSystem"/>
This is not super cool because we just made it possible to externally say what class it should use without a recompile of code. You still need to have all the classes in your code already (think ahead) and we have made the logging any better than before. We will really get this improved in part 4 but for now we are moving in the right direction, even if it doesn’t seem like it.