Thursday, September 02, 2010

Automatically Register Implementers of a Generic Interface With Unity

It's quite a common pattern to have a generic interface that is implemented by a number of concrete types and to want to be able to instantiate the concrete implementation by the type of generic parameter.

For example, given an interface IPat<T>, concrete classes Cat and Dog, we might have CatPatter implementing IPat<Cat> and DogPatter implementing IPat<Dog>. With no IoC container, to pat our pet based on the type of pet, our code needs the explicit knowledge that the CatPatter and DogPatter classes implement IPat<T>.

With an IoC container, we can register the types so all we need to do is ask the container for an instance of IPat<T> for out type of pet, but (at least with Unity) we still need to manually register each implementation of the IPat<> interface. This is clearly not ideal!

With a bit of reflection funkery, we can automatically register types that implement a generic interface.

    1       public static void RegisterAllTypesForOpenGeneric(this IUnityContainer container, Type openGenericType, Assembly targetAssembly)

    2       {

    3          if (!openGenericType.IsGenericTypeDefinition)

    4             throw new ArgumentException("typeToRegister must be an open generic type", "typeToRegister");

    5 

    6          foreach (Type type in targetAssembly.GetExportedTypes())

    7          {

    8             if (openGenericType.IsInterface)

    9                RegisterInterfaceTypes(container, openGenericType, type, type);

   10             else

   11                RegisterBaseTypes(container, openGenericType, type, type);           

   12          }

   13       }

   14 

   15       private static void RegisterInterfaceTypes(IUnityContainer container, Type openGenericType, Type targetType, Type typeToRegister)

   16       {

   17          foreach (Type interfaceType in targetType.GetInterfaces())

   18             if (interfaceType.IsGenericType && !interfaceType.ContainsGenericParameters && openGenericType.IsAssignableFrom(interfaceType.GetGenericTypeDefinition()))

   19                container.RegisterType(interfaceType, typeToRegister);

   20       }

   21 

   22       private static void RegisterBaseTypes(IUnityContainer container, Type openGenericType, Type targetType, Type typeToRegister)

   23       {

   24          if (targetType.BaseType != null && targetType.BaseType != typeof(object))

   25             if (targetType.BaseType.IsGenericType && openGenericType.IsAssignableFrom(targetType.BaseType.GetGenericTypeDefinition()))

   26                container.RegisterType(targetType.BaseType, typeToRegister);

   27             else

   28                RegisterBaseTypes(container, openGenericType, targetType.BaseType, typeToRegister);

   29       }

and some unit tests to make sure all is playing the game:

    1    [TestClass]

    2    public class AutoRegistrationTest

    3    {

    4       [TestMethod]

    5       public void WhenPassedOpenGenericInterfaceType_AllDerivedTypesAreRegistered()

    6       {

    7          // use a strict mock to ensure no other types are registered

    8          var container = MockRepository.GenerateStrictMock<UnityContainerBase>();

    9          container.Expect(x => x.RegisterType(typeof(ITest<A>), typeof(TestA))).Return(container);

   10          container.Expect(x => x.RegisterType(typeof(ITest<B>), typeof(TestB))).Return(container);

   11          container.RegisterAllTypesForOpenGeneric(typeof(ITest<>));

   12       }

   13 

   14       [TestMethod]

   15       public void WhenPassedOpenGenericConcreteType_AllDerivedTypesAreRegistered()

   16       {

   17          // use a strict mock to ensure no other types are registered

   18          var container = MockRepository.GenerateStrictMock<UnityContainerBase>();

   19          container.Expect(x => x.RegisterType(typeof(TestBase<A>), typeof(TestA))).Return(container);

   20          container.Expect(x => x.RegisterType(typeof(TestBase<B>), typeof(TestB))).Return(container);

   21          container.RegisterAllTypesForOpenGeneric(typeof(TestBase<>));

   22       }

   23 

   24       [TestMethod]

   25       public void WhenBaseClassImplementsGenericType_DerivedTypesAreRegistered()

   26       {

   27          // use a strict mock to ensure no other types are registered

   28          var container = MockRepository.GenerateStrictMock<UnityContainerBase>();

   29          container.Expect(x => x.RegisterType(typeof(ITestBase<A>), typeof(TestBaseWithInterfaceA))).Return(container);

   30          container.Expect(x => x.RegisterType(typeof(ITestBase<B>), typeof(TestBaseWithInterfaceB))).Return(container);

   31          container.RegisterAllTypesForOpenGeneric(typeof(ITestBase<>));

   32       }

   33    }

   34 

   35    public interface ITest<T> { }

   36    public class TestBase<T> { }

   37 

   38    public class TestA : TestBase<A>, ITest<A> { }

   39    public class TestB : TestBase<B>, ITest<B> { }

   40 

   41    public class A { }

   42    public class B { }

   43 

   44    public class TestBaseWithInterface<T> : ITestBase<T> { }

   45    public interface ITestBase<T> { }

   46    public class TestBaseWithInterfaceA : TestBaseWithInterface<A> { }

   47    public class TestBaseWithInterfaceB : TestBaseWithInterface<B> { }

 

Sorry about the formatting. Ergh. UPDATE: Formatting is still not ideal, I used CopySourceAsHTML plugin for VS2008. I can't install anything here at work, so no Live Writer :-\

Labels: ,

3 Comments:

Blogger Paul said...

Okay I'm so not reading that code.

I suggest using live writer and "vs paste". Hanselman did a a decent post on some of the options recently:
http://www.hanselman.com/blog/HowToPostCodeToYourBlogAndOtherReligiousArguments.aspx

12:23 AM  
Blogger Dale Anderson said...

It's a little bit better. Still fugly. The output of the CopySourceAsHTML plugin doesn't seem to play well with the theme I have. Damn line spacing. I should probably do some work rather than frigging with this.

Thanks for the link anyways! I'll be checking this stuff out from home for sure.

10:12 AM  
Anonymous Anonymous said...

Thank you so much. It saved lot of my time!!!!

3:57 AM  

Post a Comment

Note: Only a member of this blog may post a comment.

<< Home