This post continues on from the previous post. Part 2 Enterprise Library 6, Unity 3 and MVC 4, LifetimeManagers Part 2
The MVC application with unity is starting to take shape. It would be better if the registration by convention method could select which interfaces with which lifetime manager should be registered and only the required interfaces.
The application could use 3 different lifetime managers:
- singleton
- per request
- always new
An attribute will be used to register all Types to be registered by convention. To do this, a new attribute has to be created.
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] public class UnityIoCPerRequestLifetimeAttribute : System.Attribute { public double version; public UnityIoCPerRequestLifetimeAttribute() { version = 1.0; } }
Now this attribute can be applied to the business classes
[UnityIoCPerRequestLifetimeAttribute] public class BusinessClass : IBusinessClass { }
A function is also required to add to the unity RegisterTypes extension method. This method checks if the class has a UnityIoCPerRequestLifetimeAttribute attribute and when it does, it returns all implemented interfaces types for each class.
public static class UnityHelpers { private static readonly Type[] EmptyTypes = new Type[0]; public static IEnumerable<Type> FromAllInterfacesWith_PerRequestLifetimeAttribute(Type implementationType) { Guard.ArgumentNotNull(implementationType, "implementationType"); if (implementationType.GetCustomAttributes(typeof(UnityIoCPerRequestLifetimeAttribute), true).Length > 0) { var implementationTypeAssembly = implementationType.GetTypeInfo().Assembly; var typeInfo = implementationType.GetTypeInfo(); return typeInfo.ImplementedInterfaces; } else { return EmptyTypes; } } public static IEnumerable<Type> None(Type implementationType) { return EmptyTypes; } }
The Func can then be used and all classes with the custom attribute will be registered with a per request lifetime manager .
public static void RegisterTypes(IUnityContainer container) { container.RegisterTypes(AllClasses.FromLoadedAssemblies(), UnityHelpers.FromAllInterfacesWith_PerRequestLifetimeAttribute, WithName.Default, PerRequest ) .RegisterType<IUnitOfWorkExample, UnitOfWorkExampleTest>(new TransientLifetimeManager()); }
Our log output shows the construction and destruction of the different classes.
code: https://github.com/damienbod/MvcUnityBootstrapperTestPart3
The above implementation checks for classes from Loaded Assemblies and creates a per request lifetime object for classes with the custom attribute.
Now we can simplify it again. This time the Types parameter of the RegisterTypes method will be used and all classes with the attribute will be registered when it has a matching interface.
Firstly a helper method is required to get class types which have the UnityIoCPerRequestLifetimeAttribute attribute
public static IEnumerable<Type> GetTypesWithPerRequestLifetimeAttribute(Assembly[] assemblies) { foreach (var assembly in assemblies) { foreach (Type type in assembly.GetTypes()) { if (type.GetCustomAttributes(typeof(UnityIoCPerRequestLifetimeAttribute), true).Length > 0) { yield return type; } } } }
Now we use the Types parameter to define our types and unity auto maps for us using the optional WithMappings.FromMatchingInterface helper.
public static void RegisterTypes(IUnityContainer container) { container.RegisterTypes( UnityHelpers.GetTypesWithPerRequestLifetimeAttribute(AppDomain.CurrentDomain.GetAssemblies()), WithMappings.FromMatchingInterface, WithName.Default, PerRequest ) .RegisterType<IUnitOfWorkExample, UnitOfWorkExampleTest>(new TransientLifetimeManager()); }
This setups unity just as before.
Again we can optimise the TransientLifetimeManager registrations. Lets create a new attribute and apply it to all classes as required.
Define a new attribute.
[System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] public class UnityIoCTransientLifetimeAttribute : System.Attribute { public double version; public UnityIoCTransientLifetimeAttribute() { version = 1.0; } }
Then the reflection helper method needs to be changed:
public static IEnumerable<Type> GetTypesWithCustomAttribute<T>( Assembly[] assemblies) { foreach (var assembly in assemblies) { foreach (Type type in assembly.GetTypes()) { if (type.GetCustomAttributes(typeof(T), true).Length > 0) { yield return type; } } } }
And then use it in the RegisterTypes method
public static void RegisterTypes(IUnityContainer container) { container.RegisterTypes(UnityHelpers.GetTypesWithCustomAttribute<UnityIoCPerRequestLifetimeAttribute>(AppDomain.CurrentDomain.GetAssemblies()), WithMappings.FromMatchingInterface, WithName.Default, PerRequest ) .RegisterTypes(UnityHelpers.GetTypesWithCustomAttribute<UnityIoCTransientLifetimeAttribute>(AppDomain.CurrentDomain.GetAssemblies()), WithMappings.FromMatchingInterface, WithName.Default, WithLifetime.Transient ); }
The classes are constructed again in the same way except we have a much better registration by convention implementation. The same could be done for all singleton classes.
Now who requires the complex unity configuration?
Links:
Part 1 Enterprise Library 6, Unity 3 with ASP.NET MVC 4
Part 2 Enterprise Library 6, Unity 3 and MVC 4, LifetimeManagers
Part 4 Enterprise Library 6, Unity 3 InterfaceInterceptor with MVC 4
Part 5 Enterprise Library 6, Unity 3, MVC, Validation with Interception ValidationCallHandler