Search code examples
c#dependency-injectionautofac

Autofac inject IEnumerable of generic interfaces


So, I recognize StackOverflow is littered with this question, but frequently no one explains why they want to do this. I'm hoping that by doing so a better answer floats to the top.

This guy did something close to what I want: Resolving IEnumerable of generic interfaces from Autofac container But not quite.

I recognize IGenericInterface<ObjectA> is not equivalent to IGenericInterface<ObjectB>.

That being said, I'd love to inject every IService<T> into a single constructor so that I can build a lookup. Honestly, I'd like to build an implementation very similar to DbContext.Set<T>

A few key players in my problem.

public interface IService<TMessage> where TMessage: IContractDTO
{
    IQueryable<TMessage> BuildProjection();
}

Currently I'm injecting these one at a time

public class SomeController : BaseODataController<SomeEntityDTO>
{
    public SomeController(IControllerServiceContext context, IService<SomeEntityDTO> service)
      : base(context, service)
    {

    }

    //DO STUFF
}

IControllerServiceContext is a composite interface with DbContext, AppSettings, and a few other common goodies I want in every controller.

In most cases this is good enough. However occasionally in support of logic for EntityA, I might need to do a quick lookup on B. I'd rather use IService<SomeEntityB>'s implementation of BuildProjections() than building out a redundancy in Controller A. If I inject each one I have a few that would become an 8 or 9 param constructor this way, for example

SO I got to thinking what if I was able to add an IServiceLookup to IControllerServiceContext then I would have everything I needed.

I Started down this path:

public class ServiceLookup<TContract>: IServiceLookup where TContract: BaseClass, IContractDTO
{

    public ServiceLookup(IEnumerable<IService<TContract>> services)
    {
        //Build _Services
    }

    private readonly IDictionary<Type, object> _services;

    public IService<TMessage> Service<TMessage>() where TMessage : class, IContractDTO
    {
        return (IService<TMessage>)(GetService(typeof(TMessage)));
    }

    private object GetService(Type type)
    {
        _services.TryGetValue(type, out var service);

        return service;
    }
}

For obvious reasons this can't be done with the current constructor.

But is there a way to get the dictionary that I do want, either by IIndex or an IEnumerable that I can build that dictionary of <type, object> where object is my various IService<T>?

Service lookup was built based on reading the DbContext code and simplifying the logic for DbContext.Set, which is also driven by IDictionary<Type, object>.

If through some kind of resolver parameter I can get all the IService<T>s, Extract the T types, and add them to that list, I'm off to the races.

Edit: I recognize I could inject the parameters I need to build each service into ServiceLookup and manually build my list, and that may even be the better answer... but if I can do it without all that, it would be a lot more robust, and I'm fundamentally curious if it is possible

Edit2: What I want to be able to do in implementation would look like this:

public SomeController(IControllerServiceContext context, IServiceLookup lookup)
      : base(context, service)
{
    public SomeMethod() {
       var x = lookup.Service<EntityOneDTO>().BuildProjections().FirstOrDefault();
       var y = lookup.Service<EntityTwoDTO>().BuildProjections().FirstOrDefault();

    //Do Logic that requires both EntityOne and EntityTwo  
    }
}

Solution

  • Let's assume you have the following types :

    public class MessageA { }
    public class MessageB { }
    
    public interface IService<TMessage> { }
    public class ServiceA : IService<MessageA> { }
    public class ServiceB : IService<MessageB> { }
    

    And you have a controller and you want to get a IService<MessageA> based on whatever you want.

    The first solution would be to inject all IService you may required :

    public class Controller
    {
        public Controller(IService<MessageA> serviceA, IService<MessageB> serviceB)
        {
            this._serviceA = serviceA;
            this._serviceB = serviceB; 
        }
    
        private readonly IService<MessageA> _serviceA;
        private readonly IService<MessageB> _serviceB;
    
        public void Do()
        {
            IService<MessageA> serviceA = this._serviceA;
        }
    }
    

    It works if you have few message type but not if you have more than few.

    because IService<T> is generic and Do there is no easy way to mix both world. The first solution would be to introduce a non generic interface

    public interface IService { }
    public interface IService<TMessage> : IService { }
    

    and register these type like this :

    builder.RegisterType<ServiceA>().As<IService<MessageA>>().As<IService>();
    builder.RegisterType<ServiceB>().As<IService<MessageB>>().As<IService>();
    

    Then you can have an IEnumerable<IService>. Something like that :

    public interface IServiceLookup
    {
        IService<TMessage> Get<TMessage>();
    }
    public class ServiceLookup : IServiceLookup
    {
        public ServiceLookup(IEnumerable<IService> services)
        {
            this._services = services
                .ToDictionary(s => s.GetType()
                    .GetInterfaces()
                    .First(i => i.IsGenericType 
                                && i.GetGenericTypeDefinition() == typeof(IService<>))
                    .GetGenericArguments()[0], 
                              s => s);
        }
        private readonly Dictionary<Type, IService> _services;
    
        public IService<TMessage> Get<TMessage>()
        {
            // you should check for type missing, etc. 
            return (IService<TMessage>)this._services[typeof(TMessage)];
        }
    }
    

    and then inject IServiceLookup inside your controller.

    The drawback of this solution is that it create instances of all your IService, to avoid that you can inject IEnumerable<Func<IService>>

    Another solution would be to inject IComponentContext to ServiceLookup. ComponentContext is an Autofac type from where you can resolve services.

    public class ServiceLookup : IServiceLookup
    {
        public ServiceLookup(IComponentContext context)
        {
            this._context = context;
        }
        private readonly IComponentContext _context;
    
        public IService<TMessage> Get<TMessage>()
        {
            return this._context.Resolve<IService<TMessage>>();
        }
    }