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
}
}
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>>();
}
}