Autofac 注入通用接口的 IEnumerable
Autofac inject IEnumerable of generic interfaces
所以,我知道 Whosebug 充斥着这个问题,但通常没有人解释他们为什么要这样做。我希望通过这样做,更好的答案浮出水面。
这家伙做了一些接近我想要的事情:但不完全是。
我知道 IGenericInterface<ObjectA>
不等同于 IGenericInterface<ObjectB>
。
也就是说,我很乐意将每个 IService<T>
注入到一个构造函数中,以便我可以构建查找。老实说,我想构建一个与 DbContext.Set<T>
非常相似的实现
我的问题中有几个关键因素。
public interface IService<TMessage> where TMessage: IContractDTO
{
IQueryable<TMessage> BuildProjection();
}
目前我正在一次注射这些
public class SomeController : BaseODataController<SomeEntityDTO>
{
public SomeController(IControllerServiceContext context, IService<SomeEntityDTO> service)
: base(context, service)
{
}
//DO STUFF
}
IControllerServiceContext
是一个复合接口,包含 DbContext
、AppSettings
以及我希望在每个控制器中使用的其他一些常见功能。
在大多数情况下,这已经足够了。但是偶尔为了支持 EntityA 的逻辑,我可能需要快速查找 B。我宁愿使用 IService<SomeEntityB>
的 BuildProjections()
实现,也不愿在控制器 A 中构建冗余。如果我以这种方式注入每个我有一些将成为 8 或 9 参数构造函数的函数,例如
所以我开始思考如果我能够将 IServiceLookup
添加到 IControllerServiceContext
那么我将拥有我需要的一切。
我开始走这条路:
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;
}
}
由于显而易见的原因,当前构造函数无法完成此操作。
但是有没有办法通过 IIndex
或 IEnumerable
来获取我想要的字典,我可以构建 <type, object>
的字典,其中对象是我的各种IService<T>
?
服务查找是基于阅读 DbContext
代码并简化 DbContext.Set
的逻辑而构建的,这也是由 IDictionary<Type, object>
驱动的。
如果通过某种解析器参数我可以获得所有 IService<T>
s,提取 T
类型,并将它们添加到该列表中,我就可以参加比赛了。
编辑:我知道我可以注入构建每个服务所需的参数
进入 ServiceLookup
并手动构建我的列表,这甚至可能是更好的答案......但如果我可以在没有所有这些的情况下做到这一点,它会更加强大,而且我从根本上说 好奇如果可能的话
Edit2:我希望在实施中能够做的事情如下所示:
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
}
}
假设您有以下类型:
public class MessageA { }
public class MessageB { }
public interface IService<TMessage> { }
public class ServiceA : IService<MessageA> { }
public class ServiceB : IService<MessageB> { }
你有一个控制器,你想根据自己的需要得到一个 IService<MessageA>
。
第一个解决方案是注入您可能需要的所有 IService
:
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;
}
}
如果您的消息类型很少,它会起作用,但如果您的消息类型太多,则不起作用。
因为 IService<T>
是通用的并且 Do
没有简单的方法来混合这两个世界。第一个解决方案是引入一个非通用接口
public interface IService { }
public interface IService<TMessage> : IService { }
并像这样注册这些类型:
builder.RegisterType<ServiceA>().As<IService<MessageA>>().As<IService>();
builder.RegisterType<ServiceB>().As<IService<MessageB>>().As<IService>();
然后你可以有一个IEnumerable<IService>
。类似的东西:
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)];
}
}
然后将 IServiceLookup
注入到你的控制器中。
此解决方案的缺点是它会创建所有 IService 的实例,以避免您可以注入 IEnumerable<Func<IService>>
另一种解决方案是将 IComponentContext
注入 ServiceLookup
。 ComponentContext
是一种 Autofac 类型,您可以从中解析服务。
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>>();
}
}
所以,我知道 Whosebug 充斥着这个问题,但通常没有人解释他们为什么要这样做。我希望通过这样做,更好的答案浮出水面。
这家伙做了一些接近我想要的事情:
我知道 IGenericInterface<ObjectA>
不等同于 IGenericInterface<ObjectB>
。
也就是说,我很乐意将每个 IService<T>
注入到一个构造函数中,以便我可以构建查找。老实说,我想构建一个与 DbContext.Set<T>
我的问题中有几个关键因素。
public interface IService<TMessage> where TMessage: IContractDTO
{
IQueryable<TMessage> BuildProjection();
}
目前我正在一次注射这些
public class SomeController : BaseODataController<SomeEntityDTO>
{
public SomeController(IControllerServiceContext context, IService<SomeEntityDTO> service)
: base(context, service)
{
}
//DO STUFF
}
IControllerServiceContext
是一个复合接口,包含 DbContext
、AppSettings
以及我希望在每个控制器中使用的其他一些常见功能。
在大多数情况下,这已经足够了。但是偶尔为了支持 EntityA 的逻辑,我可能需要快速查找 B。我宁愿使用 IService<SomeEntityB>
的 BuildProjections()
实现,也不愿在控制器 A 中构建冗余。如果我以这种方式注入每个我有一些将成为 8 或 9 参数构造函数的函数,例如
所以我开始思考如果我能够将 IServiceLookup
添加到 IControllerServiceContext
那么我将拥有我需要的一切。
我开始走这条路:
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;
}
}
由于显而易见的原因,当前构造函数无法完成此操作。
但是有没有办法通过 IIndex
或 IEnumerable
来获取我想要的字典,我可以构建 <type, object>
的字典,其中对象是我的各种IService<T>
?
服务查找是基于阅读 DbContext
代码并简化 DbContext.Set
的逻辑而构建的,这也是由 IDictionary<Type, object>
驱动的。
如果通过某种解析器参数我可以获得所有 IService<T>
s,提取 T
类型,并将它们添加到该列表中,我就可以参加比赛了。
编辑:我知道我可以注入构建每个服务所需的参数
进入 ServiceLookup
并手动构建我的列表,这甚至可能是更好的答案......但如果我可以在没有所有这些的情况下做到这一点,它会更加强大,而且我从根本上说 好奇如果可能的话
Edit2:我希望在实施中能够做的事情如下所示:
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
}
}
假设您有以下类型:
public class MessageA { }
public class MessageB { }
public interface IService<TMessage> { }
public class ServiceA : IService<MessageA> { }
public class ServiceB : IService<MessageB> { }
你有一个控制器,你想根据自己的需要得到一个 IService<MessageA>
。
第一个解决方案是注入您可能需要的所有 IService
:
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;
}
}
如果您的消息类型很少,它会起作用,但如果您的消息类型太多,则不起作用。
因为 IService<T>
是通用的并且 Do
没有简单的方法来混合这两个世界。第一个解决方案是引入一个非通用接口
public interface IService { }
public interface IService<TMessage> : IService { }
并像这样注册这些类型:
builder.RegisterType<ServiceA>().As<IService<MessageA>>().As<IService>();
builder.RegisterType<ServiceB>().As<IService<MessageB>>().As<IService>();
然后你可以有一个IEnumerable<IService>
。类似的东西:
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)];
}
}
然后将 IServiceLookup
注入到你的控制器中。
此解决方案的缺点是它会创建所有 IService 的实例,以避免您可以注入 IEnumerable<Func<IService>>
另一种解决方案是将 IComponentContext
注入 ServiceLookup
。 ComponentContext
是一种 Autofac 类型,您可以从中解析服务。
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>>();
}
}