Thursday, August 27, 2015

IEnumerable Unity Injection

A few years ago I blogged about how to add Lazy Unity Injection, and then a year after that Unity 3 added that feature. Recently I had to dust off this old code to do something similar...

I wanted to use Unity to inject a collection of registered types into one of my services. To do this directly from the container you would use named registrations and ResolveAll. However if you just try to resolve an IEnumerable of a type in a constructor, then Unity will just try to use Resolve and thus throw an InvalidOperationException.

We can easily "fix" this by registering an extension with our container.

Extension

public class EnumerableContainerExtension : UnityContainerExtension
{
    protected override void Initialize()
    {
        Context.Policies.Set<IBuildPlanPolicy>(
            new EnumerableBuildPlanPolicy(),
            typeof(IEnumerable<>));
    }
 
    private class EnumerableBuildPlanPolicy : IBuildPlanPolicy
    {
        public void BuildUp(IBuilderContext context)
        {
            if (context.Existing != null)
                return;
 
            var container = context.NewBuildUp<IUnityContainer>();
            var typeToBuild = context.BuildKey.Type.GetGenericArguments()[0];
 
            context.Existing = container.ResolveAll(typeToBuild);
 
            DynamicMethodConstructorStrategy.SetPerBuildSingleton(context);
        }
    }
}

Unit Tests

public class ExampleService
{
    public ExampleService(IEnumerable<IExample> examples)
    {
        Examples = examples.ToList();
    }
 
    public IList<IExample> Examples { get; private set; }
}
 
public interface IExample
{
    int Id { get; }
}
 
public class ZeroExample : IExample
{
    public int Id { get { return 0; } }
}
 
public class OneExample : IExample
{
    public int Id { get { return 1; } }
}
 
public class TwoExample : IExample
{
    public int Id { get { return 2; } }
}
 
public class EnumerableContainerExtensionTests
{
    [Fact]
    public void NoExtensionTest()
    {
        using (var container = new UnityContainer())
        {
            container.RegisterType<IExample, ZeroExample>();
            container.RegisterType<IExample, OneExample>("One");
            container.RegisterType<IExample, TwoExample>("Two");
 
            var example = container.Resolve<IExample>();
            Assert.Equal(0, example.Id);
 
            // ReSharper disable once AccessToDisposedClosure
            var failedEx = Assert.Throws<ResolutionFailedException>(
                () => container.Resolve<ExampleService>());
            var invalidEx = Assert.IsType<InvalidOperationException>(
                failedEx.InnerException);
            Assert.Contains("IEnumerable`1[ConfigDemo2.IExample]",
                invalidEx.Message);
        }
    }
 
    [Fact]
    public void ExtensionTest()
    {
        using (var container = new UnityContainer())
        {
            container.AddNewExtension<EnumerableContainerExtension>();
 
            container.RegisterType<IExample, ZeroExample>();
            container.RegisterType<IExample, OneExample>("One");
            container.RegisterType<IExample, TwoExample>("Two");
 
            var example = container.Resolve<IExample>();
            Assert.Equal(0, example.Id);
 
            var service = container.Resolve<ExampleService>();
            Assert.Equal(2, service.Examples.Count);
            Assert.Equal(1, service.Examples[0].Id);
            Assert.Equal(2, service.Examples[1].Id);
        }
    }
 
    [Fact]
    public void LifetimeTest()
    {
        using (var container = new UnityContainer())
        {
            container.AddNewExtension<EnumerableContainerExtension>();
 
            container.RegisterType<IExample, OneExample>("One", 
                new ContainerControlledLifetimeManager());
            container.RegisterType<IExample, TwoExample>("Two",
                new TransientLifetimeManager());
 
            var service1 = container.Resolve<ExampleService>();
            var service2 = container.Resolve<ExampleService>();
 
            Assert.Same(service1.Examples[0], service2.Examples[0]);
            Assert.NotSame(service1.Examples[1], service2.Examples[1]);
        }
    }
}

Enjoy,
Tom

No comments:

Post a Comment

Real Time Web Analytics