Unity – Part 6: Registration by Convention
Introduction
OK, sixth post on this series, long overdue. You will find the fifth (Injecting Values) here, the fourth (Extensions) here, the third (Aspect-Oriented Programming) here, the second (Dependency Injection) here and the first one (Introduction) here.
This time I’m going to talk about something that came out in Unity 3, automatic (or convention-based) registration of components.
Automatic Registration
Normally, you register your components by using one of the Register* methods declared in IUnityContainer. The problem is that this has to be done explicitly for each component. Well, since Unity 3, we now have an automatic, convention-based mechanism.
The automatic configuration has two options:
- Using the RegisterTypes method overload that takes several parameters: one for each type to map, a delegate for choosing the registration keys, a delegate for choosing the name of the registration and finally another one for selecting the lifetime manager;
- Using the RegisterTypes overload that takes a single instance of a RegistrationConvention-derived class.
So, here’s an example of the first approach:
1: unity.RegisterTypes(AllClasses.FromAssemblies(new Assembly[] { Assembly.GetExecutingAssembly() }), WithMappings.FromAllInterfacesInSameAssembly, WithName.TypeName, WithLifetime.ContainerControlled);
This will map all of the types that implement interfaces in the current assembly from their interfaces, provided they belong to the same assembly of their implementation, with a name equal to the implementation type and with a lifetime of singleton.
A more sensible approach, however, might be instead:
1: unity.RegisterTypes(AllClasses.FromAssemblies(new Assembly[] { Assembly.GetExecutingAssembly() }).Where(x => (x.IsPublic == true) && (x.GetInterfaces().Any() == true) && (x.IsAbstract == false) && (x.IsClass == true)), WithMappings.FromAllInterfacesInSameAssembly, type => (unity.Registrations.Select(x => x.RegisteredType).Any(r => type.GetInterfaces().Contains(r) == true) == true) ? WithName.TypeName(type) : WithName.Default(type), WithLifetime.ContainerControlled);
This has the following advantage: maps the first found implementation with an empty name, and the others with a name equal to the implementation type.
A RegistrationConvention class could be like this:
1: public class InterfaceToTypeConvention : RegistrationConvention
2: {
3: private readonly IUnityContainer unity;
4: private readonly IEnumerable<Type> types;
5:
6: public InterfaceToTypeConvention(IUnityContainer unity, params Assembly [] assemblies) : this(unity, assemblies.SelectMany(a => a.GetExportedTypes()).ToArray())
7: {
8: this.unity = unity;
9: }
10:
11: public InterfaceToTypeConvention(IUnityContainer unity, params Type[] types)
12: {
13: this.unity = unity;
14: this.types = types ?? Enumerable.Empty<Type>();
15: }
16:
17: public override Func<Type, IEnumerable<Type>> GetFromTypes()
18: {
19: return (WithMappings.FromAllInterfacesInSameAssembly);
20: }
21:
22: public override Func<Type, IEnumerable<InjectionMember>> GetInjectionMembers()
23: {
24: return (x => Enumerable.Empty<InjectionMember>());
25: }
26:
27: public override Func<Type, LifetimeManager> GetLifetimeManager()
28: {
29: return (WithLifetime.ContainerControlled);
30: }
31:
32: public override Func<Type, String> GetName()
33: {
34: return (type => (this.unity.Registrations.Select(x => x.RegisteredType).Any(r => type.GetInterfaces().Contains(r) == true) == true) ? WithName.TypeName(type) : WithName.Default(type));
35: }
36:
37: public override IEnumerable<Type> GetTypes()
38: {
39: return (this.types.Where(x => (x.IsPublic == true) && (x.GetInterfaces().Any() == true) && (x.IsAbstract == false) && (x.IsClass == true)));
40: }
41: }
And its registration:
1: unity.RegisterTypes(new InterfaceToTypeConvention(unity, Assembly.GetExecutingAssembly()));
Obviously, this approach is more reusable, the same convention class can be carried over to different projects.
One thing worth mentioning: if you specify a lifetime manager, anyone other than null, each registered type will be mapped to itself, besides to its interfaces. For example, a class MyService that implements IMyService will be mapped both as IMyService –> MyService and MyService –> MyService. This is by design.
Conclusion
And that’s it for automatic configurations. Now it’s up to you to customize how you want things to be registered: name of the registration, lifetime, etc.
Stay tuned for more on Unity soon!