namespace LocalJoost.Utilities.Unity { public interface IUnityResolver { Resolve<T>(); } }Then, I define a UnityResolver, which is basically a wrapping around a UnityContainer class:
using System; using System.Configuration; using Microsoft.Practices.Unity; using Microsoft.Practices.Unity.Configuration; namespace LocalJoost.Utilities.Unity { /// <summary> /// Base class for unity resolvers /// </summary> [Serializable] public class UnityResolver : IUnityResolver { protected static string _defaultContainer = "Default"; protected IUnityContainer Container{get; set;} /// <summary> /// Initializes a new instance of the UnityResolver /// Override this constructor if you want to write your own default
/// behaviour. /// Register in code by adding lines like: /// Container.RegisterType(Type.GetType("NameSpace.IMyClass",true), /// Type.GetType("NameSpace.MyClass",true)); /// </summary> public UnityResolver() : this(_defaultContainer) { } /// <summary> /// Initializes a new instance of the UnityResolver class. /// </summary> /// <param name="containerName">Name of the container.</param> public UnityResolver(string containerName) { Container = new UnityContainer(); var section = ConfigurationManager.GetSection("unity") as UnityConfigurationSection; if (section != null) { var containerConfiguration = section.Containers[containerName]; if (containerConfiguration != null) { section.Containers[containerName].Configure(Container); } } } /// <summary> /// Resolves an instance of T /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public T Resolve<T>() { return Container.Resolve<T>(); } } }
You can use this class directly, by calling new UnityResolver(“MyContainer”).Resolve<IMyType>(). The UnityResolver looks for a Unity container named “Default” in your configuration file. If that is not present, it creates an empty container. Use of the latter feature is described below.
This is not very efficient when all your classes are sitting into one and the same container, and you may want to have some consistent behavior of your classes during unit testing with mockups. So I created the UnityFactory class that can accept a resolver and hold it:
using System; using System.Configuration; using System.Reflection; using System.Web; namespace LocalJoost.Utilities.Unity { /// <summary> /// Static helper class for shortcutting Unity instantiated /// classes /// </summary> public class UnityFactory { /// <summary> /// Method to set the resolver manually - use this for unit testing /// </summary> /// <param name="resolver">The resolver.</param> public static void SetResolver( IUnityResolver resolver) { Resolver = resolver; } /// <summary> /// Gets a resolver from configuration. /// </summary> /// <returns></returns> private static IUnityResolver GetResolverFromConfiguration() { var configuredDefaultResolver = ConfigurationManager.AppSettings["UnityResolver"]; if (!string.IsNullOrEmpty(configuredDefaultResolver)) { var specParts = configuredDefaultResolver.Split(','); var ass = Assembly.Load(specParts[1]); var objType = ass.GetType(specParts[0]); return Activator.CreateInstance(objType) as IUnityResolver; } return null; } /// <summary> /// Gets the instance of an object via an interface /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public static T GetInstance<T>() { // First, make sure there is a resolver. // If none is defined, try to load one from configuration // If that fails too, use the default resolver if (Resolver == null) { Resolver = GetResolverFromConfiguration() ?? new UnityResolver(); } // Then, resolve the interface to an object instance return Resolver.Resolve<T>(); } #region Properties /// <summary> /// Gets or sets the resolver. Uses Http context or static variable /// to store a created resolver /// </summary> /// <value>The resolver.</value> private static IUnityResolver Resolver { get { if (HttpContext.Current == null) { return _resolver; } return HttpContext.Current.Application["__UnityResolver"] as IUnityResolver; } set { if (HttpContext.Current == null) { _resolver = value; } else { HttpContext.Current.Application["__UnityResolver"] = value; } } } private static IUnityResolver _resolver; #endregion } }
Usage of this class is UnityFactory.Resolve<IMyType>(). And presto, you have got a reference to your implementation class. That’s all there is. Except for some configuration, of course ;-).
Another feature of this class is that it looks for a config setting “UnityResolver”. If that is present, it tries to load that class for a resolver in stead of the default UnityResolver. For instance, you can subclass UnityResolver, override the default constructor and define your interface-to-implementation mapping in code. Now this may look strange, because what is the point of decoupling classes and then make the mappings in code again? Well, for a scenario in which you want to use Unity for unit testing with mockups, this makes sense – when you want to deliver your production application without the necessity for a (possible very large) unity mapping section in your configuration file. If you want to register objects from code, you can do it for instance like this in the constructor of your UnityResolver override:
Container.RegisterType( Type.GetType("SomeNamespace.IMyInterface,SomeNamespace", true), Type.GetType("SomeOtherNamespace.MyImplementation,SomeOtherNamespace", true));
where "SomeNamespace" and "SomeOtherNamespace" after the comma are assembly names. If you use this construct, because it looks nice in NDepend and you don't have to make references, make sure your set the second parameter of Type.GetType, throwOnError, to true or it will fail silently and you might spend an uncomfortable long time debugging (I am talking from experience here). Personally I would go for typeof in stead of using Type.GetType but that is a matter of taste.
As a last feature, in unit testing scenarios, you can make another subclass of UnityResolver, call UnityFactory.SetResolver(myResolver) and the UnityFactory will store your resolver in a static variable (or the application context). Subsequently, all your classes will use the mapping logic defined in your own resolver, which makes a great starting point for mockup testing.
I hope this little sample will make decoupling, unit testing and mockup objects using Unity a bit more accessible.
No comments:
Post a Comment