Adding StructureMaps Registry functionality and a static wrapper to Unity
Inversion of Control (IoC) is a nice abstract design principle to get decoupling in a software architecture, Jeremy D. Miller has two nice articles about what IoC is and when to use it. I’ve used IoC primarily for decoupling parts of a system, whether it be to allow better unit testing or just less to reduce the coupling in the system. Jeremy D. Miller actually created the first .NET IoC container when he was really writing a ORM, he created such a flexible configuration layer that he stopped and started reading about IoC in the java world. He looked at his colleagues at ThoughtWorks who developed PicoContainer and saw that he could use the configuration layer to create a IoC container for .NET, he called this container StructureMap, since than a lot of IoC containers where created for .NET. In my company we’ve got the (very limiting) policy to use the Microsoft solution if it’s available and most of the times when there isn’t one we don’t use it at all. So we’ve got to use Unity, which has some cool features, but still has to mature a bit. In this post I’ll talk about a few extensions I’ve made for the Unity container.
A static (singleton) wrapper for unity
Most IoC containers allow to write statements such as IoC.Resolve<ILogger>() but for Unity the P&P team wanted support for multiple containers existing next to each other, which in large systems might be a good solution but for smaller systems is just a hassle to pass around the reference to the container. So you’ll see a lot of IoC.Instance.Resolve<ILogger>() singleton’s being created, in my opinion the .Instance. is a bit ugly and redundant. I’d like to be able to write IoC.Resolve<ILogger>(), so I had to create a static class IoC and add static methods to wrap the unity container. Below is my wrapper code.
/***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Unity.Single.IoC. * * The Initial Developer of the Original Code is * Davy Landman. * Portions created by the Initial Developer are Copyright (C) 2009 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ using System; using System.Collections.Generic; using Microsoft.Practices.Unity; namespace Unity.Single { /// <include file='IoCComments.xml' path='doc/members/member[@name="T:Unity.Single.IoC"]/*'/> public sealed class IoC { #region Lazy-Singleton private IoC() { } private static IUnityContainer instance { get { return Nested.instance; } } class Nested { static Nested() { } internal static readonly IUnityContainer instance = new UnityContainer(); } #endregion #region IoC Wrapper functions /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.BuildUp``1(``0)"]/*'/> public static T BuildUp<T>(T existing) { return instance.BuildUp<T>(existing); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.BuildUp``1(``0,System.String)"]/*'/> public static T BuildUp<T>(T existing, string name) { return instance.BuildUp<T>(existing, name); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.BuildUp(System.Type,System.Object)"]/*'/> public static object BuildUp(Type t, object existing) { return instance.BuildUp(t, existing); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.BuildUp(System.Type,System.Object,System.String)"]/*'/> public static object BuildUp(Type t, object existing, string name) { return instance.BuildUp(t, existing, name); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Resolve``1"]/*'/> public static T Resolve<T>() { return instance.Resolve<T>(); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Resolve``1(System.String)"]/*'/> public static T Resolve<T>(string name) { return instance.Resolve<T>(name); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Resolve(System.Type)"]/*'/> public static object Resolve(Type t) { return instance.Resolve(t); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Resolve(System.Type,System.String)"]/*'/> public static object Resolve(Type t, string name) { return instance.Resolve(t, name); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.ResolveAll``1"]/*'/> public static IEnumerable<T> ResolveAll<T>() { return instance.ResolveAll<T>(); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.ResolveAll(System.Type)"]/*'/> public static IEnumerable<object> ResolveAll(Type t) { return instance.ResolveAll(t); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Teardown(System.Object)"]/*'/> public static void Teardown(object o) { instance.Teardown(o); } #endregion /// <summary> /// Configure the IoC /// </summary> public static class Configure { /// <summary> /// Configure the IoC using by calling the supplied configurator. /// </summary> /// <typeparam name="TConfigurator">The configurator to use</typeparam> public static void From<TConfigurator>() where TConfigurator : IUnityContainerConfigurator, new() { From(new TConfigurator()); } /// <summary> /// Configure the IoC using by calling the supplied configurator. /// </summary> /// <param name="configurationInterface">The configurator instance to use</param> public static void From(IUnityContainerConfigurator configurationInterface) { configurationInterface.Configure(instance); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterInstance``1(``0)"]/*'/> public static IUnityContainer RegisterInstance<TInterface>(TInterface instance) { return IoC.instance.RegisterInstance<TInterface>(instance); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterInstance``1(System.String,``0)"]/*'/> public static IUnityContainer RegisterInstance<TInterface>(string name, TInterface instance) { return IoC.instance.RegisterInstance<TInterface>(name, instance); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterInstance(System.Type,System.Object)"]/*'/> public static IUnityContainer RegisterInstance(Type t, object instance) { return IoC.instance.RegisterInstance(t, instance); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterInstance``1(``0,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterInstance<TInterface>(TInterface instance, LifetimeManager lifetimeManager) { return IoC.instance.RegisterInstance<TInterface>(instance, lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterInstance``1(System.String,``0,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterInstance<TInterface>(string name, TInterface instance, LifetimeManager lifetimeManager) { return IoC.instance.RegisterInstance<TInterface>(name, instance, lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterInstance(System.Type,System.Object,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterInstance(Type t, object instance, LifetimeManager lifetimeManager) { return IoC.instance.RegisterInstance(t, instance, lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterInstance(System.Type,System.String,System.Object)"]/*'/> public static IUnityContainer RegisterInstance(Type t, string name, object instance) { return IoC.instance.RegisterInstance(t, name, instance); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterInstance(System.Type,System.String,System.Object,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterInstance(Type t, string name, object instance, LifetimeManager lifetime) { return IoC.instance.RegisterInstance(t, name, instance, lifetime); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType``2"]/*'/> public static IUnityContainer RegisterType<TFrom, TTo>() where TTo : TFrom { return instance.RegisterType<TFrom, TTo>(); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType``1(Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterType<T>(LifetimeManager lifetimeManager) { return instance.RegisterType<T>(lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType``2(Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterType<TFrom, TTo>(LifetimeManager lifetimeManager) where TTo : TFrom { return instance.RegisterType<TFrom, TTo>(lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType``2(System.String)"]/*'/> public static IUnityContainer RegisterType<TFrom, TTo>(string name) where TTo : TFrom { return instance.RegisterType<TFrom, TTo>(name); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType``1(System.String,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterType<T>(string name, LifetimeManager lifetimeManager) { return instance.RegisterType<T>(name, lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType``2(System.String,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterType<TFrom, TTo>(string name, LifetimeManager lifetimeManager) where TTo : TFrom { return instance.RegisterType<TFrom, TTo>(name, lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType(System.Type,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterType(Type t, LifetimeManager lifetimeManager) { return instance.RegisterType(t, lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType(System.Type,System.Type)"]/*'/> public static IUnityContainer RegisterType(Type from, Type to) { return instance.RegisterType(from, to); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType(System.Type,System.String,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterType(Type t, string name, LifetimeManager lifetimeManager) { return instance.RegisterType(t, name, lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType(System.Type,System.Type,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterType(Type from, Type to, LifetimeManager lifetimeManager) { return instance.RegisterType(from, to, lifetimeManager); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType(System.Type,System.Type,System.String)"]/*'/> public static IUnityContainer RegisterType(Type from, Type to, string name) { return instance.RegisterType(from, to, name); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Configure.RegisterType(System.Type,System.Type,System.String,Microsoft.Practices.Unity.LifetimeManager)"]/*'/> public static IUnityContainer RegisterType(Type from, Type to, string name, LifetimeManager lifetimeManager) { return instance.RegisterType(from, to, name, lifetimeManager); } } public static class Enterprise { /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Enterprise.AddExtension(Microsoft.Practices.Unity.UnityContainerExtension)"]/*'/> public static IUnityContainer AddExtension(UnityContainerExtension extension) { return instance.AddExtension(extension); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Enterprise.AddNewExtension``1"]/*'/> public static IUnityContainer AddNewExtension<TExtension>() where TExtension : UnityContainerExtension, new() { return instance.AddNewExtension<TExtension>(); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Enterprise.Configure``1"]/*'/> public static TConfigurator Configure<TConfigurator>() where TConfigurator : IUnityContainerExtensionConfigurator { return instance.Configure<TConfigurator>(); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Enterprise.Configure(System.Type)"]/*'/> public static object Configure(Type configurationInterface) { return instance.Configure(configurationInterface); } /// <include file='IoCComments.xml' path='doc/members/member[@name="M:Unity.Single.IoC.Enterprise.CreateChildContainer"]/*'/> public static IUnityContainer CreateChildContainer() { return instance.CreateChildContainer(); } } } /// <summary> /// An interface which must be implemented to create a configurator class for the UnityContainer. /// </summary> public interface IUnityContainerConfigurator { /// <summary> /// This method will be called to actually configure the container. /// </summary> /// <param name="destination">The container to configure.</param> void Configure(IUnityContainer destination); } }
The XML comments are in a separate file because they are from unity which has a MsPL which is not compliant with the license I’ve chosen, separating them also reduces the documentation clutter. Just place the comment file in the same directory as the source (which can also be downloaded directly).
I’ve divided the functions in three parts: basic stuff (Resolving and Building), configuration and more Enterprisy (really not fond of the name, if someone knows a better name please do tell) functionality. Below is a sample of the usage of this IoC wrapper.
using System; using Unity.Single; namespace TestApplication { public interface ILogger { void Write(string message); } public class Logger1 : ILogger { public void Write(string message) { Console.Write("Logger1: {0}", message); } } public class Logger2 : ILogger { public void Write(string message) { Console.Write("Logger2: {0}", message); } } class Program { static void Main(string[] args) { IoC.Configure.RegisterType<ILogger, Logger1>(); IoC.Resolve<ILogger>().Write("First call"); Console.WriteLine(); IoC.Configure.RegisterType<ILogger, Logger2>(); IoC.Resolve<ILogger>().Write("Second call"); Console.ReadLine(); } } }
StructureMaps Registry functionality
I’ve used StructureMap for a few private projects and I really like the Registry DSL, Unity also has a DSL to do the configuration but doesn’t offer the opportunity to group the configuration in separate units of configuration as StructureMaps Registry class does. This will decrease the complexity in your application initialization, the wire-up code does not need to know which Repository should be used for the IRepository interface. The architecture just offers a default configuration for the repositories and the wire-up code passes that configuration to the IoC container. I’ve created a interface which should be implemented to be a configurator. And that just it. I’ve extracted the relevant code below.
/// <summary> /// An interface which must be implemented to create a configurator class for the UnityContainer. /// </summary> public interface IUnityContainerConfigurator { /// <summary> /// This method will be called to actually configure the container. /// </summary> /// <param name="destination">The container to configure.</param> void Configure(IUnityContainer destination); } /// <summary> /// Configure the IoC /// </summary> public static class Configure { /// <summary> /// Configure the IoC using by calling the supplied configurator. /// </summary> /// <typeparam name="TConfigurator">The configurator to use</typeparam> public static void From<TConfigurator>() where TConfigurator : IUnityContainerConfigurator, new() { From(new TConfigurator()); } /// <summary> /// Configure the IoC using by calling the supplied configurator. /// </summary> /// <param name="configurationInterface">The configurator instance to use</param> public static void From(IUnityContainerConfigurator configurationInterface) { configurationInterface.Configure(instance); } }
Below is an example usage of this. The build-up application does not need to know which repository to map to the IProductRepository, it leaves that responsibility to the Architecture itself. You can even hide the real Repository implementation because the mapping is done inside the same namespace.
namespace TestApplication.Contract { // very simple repository public interface IRepository<T> { T Get(Int32 id); void Add(T newEntity); IEnumerable<T> List(); } public interface IProductRepository : IRepository<Product> { // simple example IEnumerable<Product> ListCheaperThan(Decimal maximumPrice); } public interface IEmail { Boolean Send(MailMessage message); } } namespace TestApplication.Architecture.EntityFramework { using TestApplication.Contract; using Unity.Single; class Repository<T> : IRepository<T> { protected ObjectContext<T> context; public Repository () { context = new ObjectContext<T>(); } #region IRepository<T> Members public T Get(int id) { return context.RetrieveById(id); } public void Add(T newEntity) { context.Insert(newEntity); } public IEnumerable<T> List() { return context.EntitySet; } #endregion } class ProductRepository : Repository<Product>, IProductRepository { #region IProductRepository Members public IEnumerable<Product> ListCheaperThan(decimal maximumPrice) { return context.EntitySet.Where(prod => prod.Price < maximumPrice); } #endregion } public class DefaultConfiguration : IUnityContainerConfigurator { public void Configure(Microsoft.Practices.Unity.IUnityContainer destination) { destination.RegisterType<IProductRepository, ProductRepository>(); } } } namespace TestApplication.WebInterface { using Unity.Single; using TestApplication.Contract; using TestApplication.Architecture.EntityFramework; class ASPMailer : IEmail { public bool Send(System.Net.Mail.MailMessage message) { throw new System.NotImplementedException(); } } public class Application { public void Startup() { IoC.Configure.From<DefaultConfiguration>(); IoC.Configure.RegisterType<IEmail, ASPMailer>(); } } }
Have fun :)
Tags: architecture, c#, Design Pattern