CHAPTER 5
Unity is highly extensible. This chapter talks about some of the various aspects by which it can be extended, and provides some examples of useful functionality that is not included out of the box.
It is certainly possible to create new lifetime managers. One example might be a pooled lifetime manager that creates up to N instances of a type and then returns them in a round robin fashion. Let’s see how we could implement one. Enter class PooledLifetimeManager
public sealed class PooledLifetimeManager : LifetimeManager, IDisposable { private const Int32 DefaultPoolSize = 10; private readonly List<Object> pool; private Int32 index = -1;
public PooledLifetimeManager(Int32 poolSize) { this.pool = new List<Object>(poolSize); }
public PooledLifetimeManager() : this(DefaultPoolSize) { }
public override Object GetValue() { if (this.pool.Count < this.pool.Capacity) { return (null); } else { if (++this.index == this.pool.Capacity) { this.index = 0; }
return (this.pool[this.index]); } }
public override void SetValue(Object newValue) { this.pool.Add(newValue); }
public override void RemoveValue() { this.Dispose(); } public void Dispose() { foreach (var disposable in this.pool.OfType<IDisposable>()) { disposable.Dispose(); } this.pool.Clear(); this.index = -1; } } |
This class will return up to 10 new instances by default; after that, Resolve will return one of the existing ones in round robin order. To the Resolve method, the lifetime manager does not matter; it is totally transparent.
When implementing a lifetime manager, you need to know that its methods are called in this order:
For reusability purposes, Unity allows us to have conventions classes that wrap these rules. These classes need to inherit from RegistrationConvention and one class with the exact same behavior is:
public class InterfaceToClassConvention : RegistrationConvention { private readonly IUnityContainer unity; private readonly IEnumerable<Type> types; public InterfaceToClassConvention(IUnityContainer unity, params Assembly [] assemblies) : this(unity, assemblies .SelectMany(a => a.GetExportedTypes()).ToArray()) { this.unity = unity; } public InterfaceToClassConvention(IUnityContainer unity, params Type[] types) { this.unity = unity; this.types = types ?? Enumerable.Empty<Type>(); } public override Func<Type, IEnumerable<Type>> GetFromTypes() { return (WithMappings.FromAllInterfacesInSameAssembly); } public override Func<Type, IEnumerable<InjectionMember>> GetInjectionMembers() { return (x => Enumerable.Empty<InjectionMember>()); } public override Func<Type, LifetimeManager> GetLifetimeManager() { return (WithLifetime.None); } public override Func<Type, String> GetName() { return (type => (this.unity.Registrations.Select(x => x.RegisteredType) .Any(r => type.GetInterfaces().Contains(r))) ? WithName.TypeName(type) : WithName.Default(type)); } public override IEnumerable<Type> GetTypes() { return (this.types.Where(x => (x.IsPublic) && (x.GetInterfaces().Any()) && (!x.IsAbstract) && (x.IsClass))); } } |
And you would use it as:
unity.RegisterTypes(new InterfaceToClassConvention(unity, Assembly.GetExecutingAssembly())); |
It is a nice feature to have at hand.
Unity allows us to register extensions to augment its basic functionality. These extensions can be registered either by code or by XML configuration. One such registration might be one that sets up interception and then goes through all registered types (current and future) and sets its interceptors accordingly (see chapter Chapter 4 Aspect-Oriented Programming):
public class DefaultInterception : Interception { private static readonly IInterceptor [] interceptors = typeof(IInterceptor) .Assembly .GetExportedTypes() .Where ( type => (typeof(IInterceptor).IsAssignableFrom(type) == true) && (type.IsAbstract == false) && (type.IsInterface == false) ) .Select(type => Activator.CreateInstance(type) as IInterceptor) .ToArray(); protected override void Initialize() { base.Initialize(); var configSource = ConfigurationSourceFactory.Create(); var section = configSource.GetSection( PolicyInjectionSettings.SectionName) as PolicyInjectionSettings; if (section != null) { PolicyInjectionSettings.ConfigureContainer(this.Container, configSource); }
foreach (var registration in this.Container.Registrations) { if (registration.RegisteredType.Assembly != typeof(IUnityContainer).Assembly) { this.SetInterceptorFor(registration.RegisteredType, registration.MappedToType, registration.Name); } }
this.Context.Registering += delegate(Object sender, RegisterEventArgs e) { this.SetInterceptorFor(e.TypeFrom ?? e.TypeTo, e.TypeTo, e.Name); };
this.Context.RegisteringInstance += delegate(Object sender, RegisterInstanceEventArgs e) { this.SetInterceptorFor(e.RegisteredType, e.Instance.GetType(), e.Name); }; } private void SetInterceptorFor(Type typeFrom, Type typeTo, String name) { foreach (var interceptor in interceptors) { if ((interceptor.CanIntercept(typeFrom)) && (interceptor.GetInterceptableMethods(typeFrom, typeTo).Any())) { if (interceptor is IInstanceInterceptor) { this.Container.Configure<Interception>().SetInterceptorFor( typeFrom, name, interceptor as IInstanceInterceptor); } else if (interceptor is ITypeInterceptor) { this.Container.Configure<Interception>().SetInterceptorFor( typeFrom, name, interceptor as ITypeInterceptor); } break; } } } } |
Tip: You will need to add a reference to the System.Configuration assembly and also the NuGet package EnterpriseLibrary.PolicyInjection. See Chapter 4 Aspect-Oriented Programming.
An extension for Unity must inherit from UnityContainerExtension; in our example, we are instead inheriting from one subclass that Unity offers for configuring interception (AOP) policies, Interception. Here we create a static list of all interceptors available, go through all registered types (Registrations collection), and set up an interceptor for each registration that can intercept its key type. At the same time, we hook up the RegisteringInstance and Registering events, and we do the same for every new registration that may exist.
We configure this extension in code as this:
unity.AddNewExtension<DefaultInterception>(); |
And in XML:
<container> <extension type="Succinctly.DefaultInterception, Succinctly" /> </container> |
Tip: Again, don’t forget to call LoadConfiguration() otherwise the extension will not be loaded.
If you need access to your extension instance, which may have been defined in XML or in code, you can get a reference to it by calling the Configure method:
var defaultInterceptionExtension = unity.Configure<DefaultInterception>(); |
Imagine we want to implement a caching call handler so that all methods that have it applied to (the first time they are called) will cache their result for some time. All subsequent calls in that period of time will not cause the method to execute but, instead, will return the cached value.
A simple implementation, making use of .NET 4’s extensible caching providers, follows as a marker attribute to be applied to cacheable methods that is also a cache handler implementation:
[Serializable] [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class CachingHandlerAttribute : HandlerAttribute, ICallHandler { private readonly Guid KeyGuid = new Guid("ECFD1B0F-0CBA-4AA1-89A0-179B636381CA"); public CachingHandlerAttribute(int hours, int minutes, int seconds) { this. Duration = new TimeSpan(hours, minutes, seconds); } public TimeSpan Duration { get; private set; } public override ICallHandler CreateHandler(IUnityContainer ignored) { return (this); } IMethodReturn ICallHandler.Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { if (this.TargetMethodReturnsVoid(input) == true) { return (getNext()(input, getNext)); } var inputs = new Object [ input.Inputs.Count ]; for (var i = 0; i < inputs.Length; ++i) { inputs [ i ] = input.Inputs [ i ]; } var cacheKey = this.CreateCacheKey(input.MethodBase, inputs); var cache = MemoryCache.Default; var cachedResult = (Object []) cache.Get(cacheKey); if (cachedResult == null) { var realReturn = getNext()(input, getNext); if (realReturn.Exception == null) { this.AddToCache(cacheKey, realReturn.ReturnValue); } return (realReturn); } var cachedReturn = input.CreateMethodReturn(cachedResult [ 0 ], input.Arguments);
return (cachedReturn); }
private Boolean TargetMethodReturnsVoid(IMethodInvocation input) { var targetMethod = input.MethodBase as MethodInfo; return ((targetMethod != null) && (targetMethod.ReturnType == typeof(void))); }
private void AddToCache(String key, Object value) { var cache = MemoryCache.Default; var cacheValue = new Object [] { value }; cache.Add(key, cacheValue, DateTime.Now + this.Duration); }
private String CreateCacheKey(MethodBase method, params Object [] inputs) { var sb = new StringBuilder(); sb.AppendFormat("{0}:", Process.GetCurrentProcess().Id); sb.AppendFormat("{0}:", KeyGuid); if (method.DeclaringType != null) { sb.Append(method.DeclaringType.FullName); } sb.Append(':'); sb.Append(method); if (inputs != null) { foreach (var input in inputs) { sb.Append(':'); if (input != null) { sb.Append(input.GetHashCode().ToString()); } } } return (sb.ToString()); } } |
Tip: You will need to add a reference to assembly System.Runtime.Caching.
This attribute can be applied to any interceptable method (see Table 2: Interception kinds and targets) with a return type other than void. When the method is called the first time, the call handler generates a key from the method name and signature, plus the hash code of all of its parameters and the current process identifier. The intercepted method is called and its result is stored under the generated cache key for a period of time. In all subsequent calls, the call handler checks the cache for an entry with the generated key and, in case it is present, returns the cached value immediately. The cache provider in use is the one returned from MemoryCache.Default, which means the cache will be stored in memory. By all means, you can switch to a different one; for example, the ASP.NET cache.
Here’s how we would apply the caching handler attribute:
[CachingHandler(1, 0, 0)] //1 hour, 0 minutes, 0 seconds void DoLongOperation(int someParameter, int anotherParameter); |
Note: In a real-life case, you would probably want to separate the ICallHandler implementation from the HandlerAttribute for better decoupling and manageability.
In chapter Chapter 3 Dependency Injection, we saw how we can configure dependencies so that our components have constructor and method parameters and properties injected automatically upon them. However, the techniques that were shown only allow two types of injected values:
This is a big limitation; fortunately, Unity offers some extension hooks for more advanced options. As a sample, let’s implement a way to inject values coming from the appSettings configuration section.
We start by defining a custom section extension, which we will use in XML configuration (if you don’t plan to use XML, just skip it):
public class AppSettingsParameterInjectionElementExtension : SectionExtension { public override void AddExtensions(SectionExtensionContext context) { context.AddElement<AppSettingsParameterValueElement>("appSettings"); } } |
Then, the ValueElement implementation; it’s a bit more complex since it has to support property, method, and constructor injection, and also convert the value read from the appSettings into the target type:
public class AppSettingsParameterValueElement : ValueElement, IDependencyResolverPolicy { private Object CreateInstance(Type parameterType) { var configurationValue = ConfigurationManager.AppSettings [this.AppSettingsKey] as Object; if (parameterType != typeof(String)) { var typeConverter = this.GetTypeConverter(parameterType); if (typeConverter != null) { configurationValue = typeConverter. ConvertFromInvariantString(configurationValue as String); } } return (configurationValue); }
private TypeConverter GetTypeConverter(Type parameterType) { if (String.IsNullOrEmpty(this.TypeConverterTypeName) == false) { return (Activator.CreateInstance(TypeResolver.ResolveType( this.TypeConverterTypeName)) as TypeConverter); } else { return (TypeDescriptor.GetConverter(parameterType)); } } public override InjectionParameterValue GetInjectionParameterValue( IUnityContainer container, Type parameterType) { var value = this.CreateInstance(parameterType); return (new InjectionParameter(parameterType, value)); } Object IDependencyResolverPolicy.Resolve(IBuilderContext context) { Type parameterType = null;
if (context.CurrentOperation is ResolvingPropertyValueOperation) { var op = context.CurrentOperation as ResolvingPropertyValueOperation; var prop = op.TypeBeingConstructed.GetProperty(op.PropertyName); parameterType = prop.PropertyType; } else if (context.CurrentOperation is ConstructorArgumentResolveOperation) { var op = context.CurrentOperation as ConstructorArgumentResolveOperation; var args = op.ConstructorSignature.Split('(')[1].Split(')')[0]; var types = args.Split(','). Select(a => Type.GetType(a.Split(' ')[0])).ToArray(); var ctor = op.TypeBeingConstructed.GetConstructor(types); parameterType = ctor.GetParameters() .Single(p => p.Name == op.ParameterName).ParameterType; } else if (context.CurrentOperation is MethodArgumentResolveOperation) { var op = context.CurrentOperation as MethodArgumentResolveOperation; var methodName = op.MethodSignature.Split('(')[0].Split(' ')[1]; var args = op.MethodSignature.Split('(')[1].Split(')')[0]; var types = args.Split(',') .Select(a => Type.GetType(a.Split(' ')[0])).ToArray(); var method = op.TypeBeingConstructed.GetMethod(methodName, types); parameterType = method.GetParameters() .Single(p => p.Name == op.ParameterName).ParameterType; } return (this.CreateInstance(parameterType)); } [ConfigurationProperty("appSettingsKey", IsRequired = true)] public String AppSettingsKey { get { return (base["appSettingsKey"] as String); } set { base["appSettingsKey"] = value; } } } |
Finally, the application attribute, which you will only need if you want to use attribute-based configuration:
[Serializable] [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class AppSettingsAttribute : DependencyResolutionAttribute { public AppSettingsAttribute(String appSettingsKey) { this.AppSettingsKey = appSettingsKey; }
public String TypeConverterTypeName { get; set; }
public String AppSettingsKey { get; private set; }
public override IDependencyResolverPolicy CreateResolver(Type typeToResolve) { return (new AppSettingsParameterValueElement() { AppSettingsKey = this.AppSettingsKey, TypeConverterTypeName = this.TypeConverterTypeName }); } } |
That’s it. Now we can apply our value injector in XML:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration. UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/> </configSections> <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <sectionExtension type="Succinctly.AppSettingsParameterInjectionElementExtension, Succinctly" /> <container> <register type="Succinctly.ILogger, Succinctly" mapTo="Succinctly. FileLogger, Succinctly" name="File"> <property name="Filename"> <appSettings appSettingsKey="LoggerFilename"/> </property> </register> </container> </unity> <appSettings> <add key="LoggerFilename" value="Log.txt" /> </appSettings> </configuration> |
Or through an attribute applied to an injected property:
public class FileLogger : ILogger { [AppSettings("LoggerFilename")] public String Filename { get; set; } public void Log(String message) { } } |
Or constructor parameter:
public class FileLogger : ILogger { public FileLogger ([AppSettings("LoggerFilename")] String filename) { this.Filename = filename; }
public String Filename { get; set; } public void Log(String message) { } } |
Or even to an injection method parameter:
public class FileLogger : ILogger { [InjectionMethod] public void Initialize([AppSettings("LoggerFilename")] String filename) { this.Filename = filename; }
public String Filename { get; set; } public void Log(String message) { } } |
Chris Tavares, one of the leading developers of Unity, has published in his Bitbucket repository a sample that shows how to implement a section extension for defining an injection factory method in XML. This injection factory is a way to create object instances by calling a static method of a class.
I advise you to look at Chris’s code. Just as an appetizer, you can configure it as this:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration. UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/> </configSections> <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <sectionExtension type="Unity.FactoryConfig.FactoryConfigExtension, Unity.FactoryConfig"/> <container> <register type="Succinctly.ILogger, Succinctly" mapTo="Succinctly. ConsoleLogger, Succinctly"> <factory type="Succinctly.LoggerFactory, Succinctly" method="Create" /> </register> </container> </unity> </configuration> |
I think it is easy to understand: There should be a LoggerFactory class that exposes a static method called Create which returns an ILogger instance. Instances of the ILogger type, when resolved by Unity, will be built using this method.
The included rules for member matching cover quite a few scenarios but one thing they do not have is logical groupings: OR and AND. Fortunately, this is very easy to achieve. We can do this by rolling our own classes that implement IMatchingRule:
public sealed class AndRule : IMatchingRule { private readonly IEnumerable<IMatchingRule> rules;
public AndRule(IEnumerable<IMatchingRule> rules) { this.rules = rules; }
public AndRule(params IMatchingRule[] rules) { this.rules = rules; }
public Boolean Matches(MethodBase member) { foreach (var rule in this.rules) { if (rule.Matches(member) == false) { return (false); } } return (true); } } public sealed class OrRule : IMatchingRule { private readonly IEnumerable<IMatchingRule> rules;
public OrRule(IEnumerable<IMatchingRule> rules) { this.rules = rules; }
public OrRule(params IMatchingRule [] rules) { this.rules = rules; }
public Boolean Matches(MethodBase member) { foreach (var rule in this.rules) { if (rule.Matches(member) == true) { return (true); } } return (false); } } |