Convention based registrations with Unity

26/09/2014

With the release of the dependency framework Unity 3.0, it was finally made possible to do convention-based registrations. It been a while since I have worked with Unity, so bear in mind that version 3.0 have since been surpassed by a version 3.5, but nevertheless convention based registration is still an option.
First, let us look at why we need convention-based registration. Convention based registration saves us from having to write boilerplate code that just registers the interface IMyRepository with MyRepository. Registrations like those are annoying to write, and often you forget to add them, resulting in run-time exceptions.
If that is the only type of registrations you do, then you lucky because the assembly Microsoft.Practices.Unity.RegistrationByConvention.dll (which you by the way need if you want to do any type of convention-based registrations), contains all you need for setting it up.
You simply do so by calling
[csharp]
container.RegisterTypes(AllClasses.FromAssemblies(this.GetType().Assembly),
WithMappings.FromMatchingInterface, WithName.Default, WithLifetime.Transient);
[/csharp]
This one-liner simply tells unity to look for all classes in the assembly this line resides in (you could extend this to other assemblies too). For every class where it can find a matching interface, by the convention that if class name is MyClass then there should be an interface named IMyClass, it does an registration with no name (indicated by WithName.Default), with a transient lifetime manager. Pretty simple, yet very powerful, saving us many lines of registrations in even a small enterprise projects.
However, it becomes even better, because you can make your own mapping rules from classes to interfaces, so even if your naming convention is not as described above you can use convention-based registrations.
In our SharePoint projects, we use a Model-View-Presenter framework to build all our webparts, thus a webpart named Person consist of a webpart class that is defined like this
[csharp]
public class Person : WebPartBase<IPersonView, IPresenter<IPersonView>>, IPersonView
[/csharp]
This lets us change the presenter logic by changing the registration, but usually we never need to do that, so for every webpart in our projects we used to have registrations like this
[csharp]
container.RegisterType<IPresenter<IPersonView>, PersonPresenter>();
[/csharp]
We could of course have made interfaces for each presenter, so the built-in convention-based registration would have worked. But creating these interface wouldn’t really add anything as all the stuff we need is in the generic IPresenter interface, so that would just be lot of boilerplate code.
But, with convention based registrations we no longer need to add these registrations by hand
Here is some sample code that does the aforementioned presenter registration.

[csharp]

private void AutoRegisterPresenters(IUnityContainer container)
{
container.RegisterTypes(AllClasses.FromAssemblies(this.GetType().Assembly), GetFromTypes, WithName.Default, WithLifetime.Transient);
}
private static IEnumerable<Type> GetFromTypes(Type type)
{
var r = new Regex("(.*)Presenter");
var match = r.Match(type.Name);
if (match.Success)
{
var view1 =
type.Assembly.GetTypes()
.FirstOrDefault(s => s.IsInterface && s.Name == string.Format("I{0}View", match.Groups[1].Value));
if (view1 != null)
{
return new Type[] { typeof(IPresenter<>).MakeGenericType(view1)};
}
var view2 =
type.Assembly.GetTypes()
.FirstOrDefault(s => s.IsInterface && s.Name == string.Format("I{0}", match.Groups[1].Value));
if (view2 != null)
{
return new Type[] { typeof(IPresenter<>).MakeGenericType(view2) };
}
}
return new Type[] {};
}
[/csharp]
The GetFromTypes method, does the magic here. For each type found by the call to AllClasses.FromAssembly(this.GetType().Assembly, it tries to find a matching interface, by the conventions that the interface should be named IView or I. If such interface is found we return the type IPresenter and we have accomplished the same as the manual registration.

One final thing I want to note is that in case you already have a registration that is also matched by one of your conventions, then you will see the following exception, at runtime.

[DuplicateTypeMappingException: An attempt to override an existing mapping was detected for type

You can avoid this either by adding a fifth boolean parameter to your .RegisterTypes call in order to force the convention-based registration to override any existing registrations. I think that is a dangerous way to go, so personally I let the convention-based registration run first, and then afterwards do the overrides needed.