Register and resolve open generic types with many generic parameters with Autofac

435 views Asked by At

I would like to resolve an open generic service due to a generic interface. I use autofac.

Each concrete service works only with concrete classes.

I can resolve only one service with single generic param [see SingleOpenGenericResolveTest]. Is it possible to register & resolve many services with many T-params [see MultiOpenGenericResolveTest]?

I added only one of concrete classes for IService but it possible to be many classes of T. (TRegion : Region, TRegion : BigRegion, etc...)

Here is NUnit 3 tests or you can download my solution here : https://www.dropbox.com/s/vqmdwb6hwmzgjrb/AutofacResolveTests.zip?dl=0

using System;
using NUnit.Framework;
using Autofac;
using System.Reflection;
using System.Linq;

namespace AutofacResolveTests
{
    public class Address<TCity, TRegion, TSomethingElse>
        where TCity : City<TRegion>, new()
        where TRegion : Region, new()
        where TSomethingElse : SomethingElse, new()
    {
        public int Id { get; set; }
        TCity City { get; set; }
        TRegion Region { get; set; }
        TSomethingElse SomethingElse { get; set; }
    }

    public class City<TRegion>
        where TRegion : Region, new()
    {
        public int Id { get; set; }
        TRegion Region { get; set; }
    }

    public class Region
    {
        public int Id { get; set; }
    }

    public class SomethingElse
    {
        public int Id { get; set; }
    }

    public interface IService<T> where T : class
    {
        void DoSomething(T entity);
    }

    public class AddressService<TAddress, TCity, TRegion, TSomethingElse> : IService<TAddress>
        where TAddress : Address<TCity, TRegion, TSomethingElse>
        where TCity : City<TRegion>, new()
        where TRegion : Region, new()
        where TSomethingElse : SomethingElse, new()
    {
        public void DoSomething(TAddress entity)
        {
            Console.WriteLine("Hello from address service");
        }
    }

    public class CityService<TCity, TRegion> : IService<TCity>
        where TCity : City<TRegion>, new()
        where TRegion : Region, new()
    {
        public void DoSomething(TCity entity)
        {
            Console.WriteLine("Hello from city service");
        }
    }

    public class RegionService<TRegion> : IService<TRegion>
        where TRegion : Region
    {
        public void DoSomething(TRegion entity)
        {
            Console.WriteLine("Hello from region service");
        }
    }

    [TestFixture]
    public class OpenGenericResolveTests
    {
        IContainer _ioc;

        [SetUp]
        public void Setup()
        {
            var container = new ContainerBuilder();
        //manual types registration - works
        /*
        container.RegisterType(typeof(CityService<City<Region>, Region>)).As(typeof(IService<City<Region>>)).AsImplementedInterfaces();
        container.RegisterType(typeof(AddressService<
            Address<City<Region>, Region, SomethingElse>, City<Region>, Region, SomethingElse
            >))
            .As(typeof(IService<
            Address<City<Region>, Region, SomethingElse>
            >)).AsImplementedInterfaces();
        */

            var type = typeof(IService<>);
            //just get all services which implements IService
            var generics = type.Assembly.GetTypes().Where(x =>
            !x.IsInterface
            && x.Name.Contains("Service")
            && x.IsGenericType
            && x.GetInterfaces().Any(i => i.GetGenericTypeDefinition() == type)
            );

            foreach (var svcType in generics)
            {
                container.RegisterGeneric(svcType)
                .As(typeof(IService<>))
                .AsImplementedInterfaces();
            }

            _ioc = container.Build();
        }

        [Test]
        public void SingleOpenGenericResolveTest()
        {
            var reg = new Region { };

            var actual = _ioc.Resolve<IService<Region>>();
            Assert.That(actual != null);

            actual.DoSomething(reg);
        }

        [Test]
        public void MultiOpenGenericResolveTest()
        {
            //works
            var actual1 = _ioc.Resolve<IService<Region>>();
            Assert.That(actual1 != null);

            //works only with manual registration
            var actual2 = _ioc.Resolve<IService<City<Region>>>();
            Assert.That(actual2 != null);

            //works only with manual registration
            var actual3 = _ioc.Resolve<IService<Address<City<Region>,Region,SomethingElse>>>();
            Assert.That(actual3 != null);
        }
    }
}
1

There are 1 answers

1
Travis Illig On

The problem is that if you resolve IService<T> but the implementation takes more than one generic like Service<T,U,V> then there's no way for Autofac to know where the other types (U, V) come from... So the way you're trying to register them with assembly scanning en masse won't work.