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

2 comments:

  1. Cocok buat yang hobi bermain game keberuntungan online dilink dominobet asia tapi pengen tetap aman dan nyaman. Dapat rekomendasi dari teman, ternyata memang worth it banget.

    ReplyDelete
  2. Tiap minggu ada event menarik, jadi nggak bosen mainnya. Kalo hoki dilink olxtoto login dan link alternatif dewapoker bisa balik modal berkali lipat dalam sehari.

    ReplyDelete