Unity Registration By Convention For Abstract Types

This post is inspired by this question on Stack Overflow: Can Unity’s “register-by-convention” resolve for abstract classes?  The basic question is: “how can i register my abstract classes and the implementation of each repository specific repository, like with the interfaces?”

The bad news is that there is no facility out of the box in Unity to register Abstract type mappings while the good news is that Unity provides a means to do this with a little coding.

The parameters to the RegisterTypes methods are Funcs which means that we can create our own functions that perform the required registration logic.

For this posting I will assume that the classes to register look something like this:

public abstract class AbstractBase { }

public abstract class AbstractRepository : AbstractBase { }

public class TestRepository : AbstractRepository { }

public class AnotherRepository : AbstractRepository { }

For the mappings AbstractBase will be mapped to TestRepository and AbstractRepository will also be mapped to TestRepository.

Let’s also assume that we want to handle generics:

public abstract class AbstractBase<T> { }

public abstract class AbstractRepository<T> : AbstractBase<T> { }

public class TestRepository<T> : AbstractRepository<T> { }

public class TestRepositoryClosed : AbstractRepository<int> { }

With this as a starting point, the first step is to create the logic that maps from abstract to concrete types:

public static class WithMappingsCustom
{
    private static readonly IEnumerable<Type> EmptyTypes =
        Enumerable.Empty<Type>();

    public static IEnumerable<Type> FromAbstractClass(Type implementationType)
    {
        if (implementationType == null)
        {
            throw new ArgumentNullException("implementationType");
        }

        List<Type> types = null;

        // Recurse over object tree to populate types with the mappedFrom types
        GetImplementedAbstractClassesToMap(implementationType, ref types);

        if (types != null)
        {
            return types;
        }
        else
        {
            return EmptyTypes;
        }
    }

    private static void GetImplementedAbstractClassesToMap(Type type,
        ref List<Type> types)
    {
        var typeInfo = type.GetTypeInfo();

        // Top level type (i.e. "MappedTo") is abstract so skip
        if (typeInfo.IsAbstract && types == null)
        {
            return;
        }

        // If type is a value type or inherits from object then there 
        // is nothing to map to or we have reached the top of the hierarchy
        if (typeInfo.IsValueType || typeInfo.BaseType == typeof(object))
        {
            return;
        }

        Type nextType = typeInfo.BaseType;
        Type nextTypeInfo = typeInfo.BaseType.GetTypeInfo();

        // If the base type is abstract we will create mapping for it
        if (nextTypeInfo.IsAbstract)
        {
            // Special handling for generics otherwise type information is not
            // populated see: 
            // http://msdn.microsoft.com/en-us/library/system.type.fullname.aspx
            if (nextTypeInfo.IsGenericTypeDefinition ||
                (nextTypeInfo.IsGenericType && nextTypeInfo.FullName == null))
            {
                nextType = nextTypeInfo.GetGenericTypeDefinition();
            }

            if (types == null)
            {
                types = new List<Type>();
            }

            types.Add(nextType);
        }

        // Recurse to get other abstract objects in hierarchy so that 
        // we can map one type to multiple abstract base classes
        GetImplementedAbstractClassesToMap(nextType, ref types);
    }
}

The above code looks at the type and walks the BaseType hierarchy to add any abstract types to the list of types that will be used as the “from” value in the mapping. Internally, a lazily instantiated List is used (to avoid object creation if there are no mappings). Iterators could also be used instead but for deep object graphs that might cause performance issues.

Now that the logic is created to generate the proper mappings then we have to use it in a call to RegisterTypes:

IUnityContainer container = new UnityContainer();

container.RegisterTypes(
    AllClasses.FromLoadedAssemblies().Where(t => !t.IsGenericTypeDefinition),
    WithMappingsCustom.FromAbstractClass,
    WithName.TypeName,
    WithLifetime.PerResolve);
            
container.RegisterTypes(
    AllClasses.FromLoadedAssemblies().Where(t => t.IsGenericTypeDefinition),
    WithMappingsCustom.FromAbstractClass,
    WithName.Default,
    WithLifetime.PerResolve);

There are two calls to RegisterTypes: the first does non-generic types the second does generic types. The reason is that it’s assumed that because we are mapping abstract classes that there will be many mappings to concrete types so will use named mappings (by using WithName.TypeName). For the generic types, it’s assumed that there will just be one registration so the default (unnamed mapping) is sufficient.

It should be working but let’s check:

AbstractRepository testRepository =
    container.Resolve<AbstractRepository>(typeof(TestRepository).Name);
Debug.Assert(testRepository.GetType() == typeof(TestRepository));

testRepository =
    container.Resolve<AbstractRepository>(typeof(AnotherRepository).Name);
Debug.Assert(testRepository.GetType() == typeof(AnotherRepository));

AbstractBase testRepository2 =
    container.Resolve<AbstractBase>(typeof(TestRepository).Name);
Debug.Assert(testRepository2.GetType() == typeof(TestRepository));

AbstractRepository<string> stringRepository =
    container.Resolve<AbstractRepository<string>>();
Debug.Assert(stringRepository.GetType() == typeof(TestRepository<string>));

AbstractRepository<int> closedRepository =
    container.Resolve<TestRepositoryClosed>();
Debug.Assert(closedRepository.GetType() == typeof(TestRepositoryClosed));
Debug.Assert(typeof(AbstractBase<int>).IsAssignableFrom(closedRepository.GetType()));

So everything is working and we are done.

Well, we could be done. But there are more changes that we can do.

Unity provides a way to formalize registration logic by using the abstract RegistrationConvention class. This provides a common method of sharing registration logic.

So instead of using our static helper class the logic can be encapsulated by a RegistrationConvention using the helper method and some values from the original post:

public class AbstractClassRegistrationConvention : RegistrationConvention
{
    private static readonly IEnumerable<InjectionMember> EmptyInjectionMember
        = new InjectionMember[0];

    public override Func<Type, IEnumerable<Type>> GetFromTypes()
    {
        return (t) => WithMappingsCustom.FromAbstractClass(t);
    }

    public override Func<Type, IEnumerable<InjectionMember>> GetInjectionMembers()
    {
        return (t) => EmptyInjectionMember;
    }

    public override Func<Type, LifetimeManager> GetLifetimeManager()
    {
        return WithLifetime.PerResolve;
    }

    public override Func<Type, string> GetName()
    {
        return WithName.TypeName;
    }

    public override IEnumerable<Type> GetTypes()
    {
        return AllClasses.FromLoadedAssemblies()
            .Where(t => !t.IsGenericTypeDefinition);
    }
}

public class AbstractGenericClassRegistrationConvention
    : AbstractClassRegistrationConvention
{
    public override IEnumerable<Type> GetTypes()
    {
        return AllClasses.FromLoadedAssemblies()
            .Where(t => t.IsGenericTypeDefinition);
    }

    public override Func<Type, string> GetName()
    {
        return WithName.Default;
    }
}

All the registration logic is now hidden so that the registration code is incredibly simple looking:

IUnityContainer container = new UnityContainer();

var convention = new AbstractClassRegistrationConvention();
container.RegisterTypes(convention);

convention = new AbstractGenericClassRegistrationConvention();
container.RegisterTypes(convention);

In this post a method for registering abstract base classes from a concrete type was demonstrated as well as a how to formalize this logic into a RegistrationConvention.

This entry was posted in Uncategorized and tagged , , . Bookmark the permalink.

Leave a comment