如何在Autofac中注入泛型接口IEnumerable<IService<T>>实现服务查找?
刚好之前做过类似的需求,用Autofac完全可以实现你想要的自动收集所有IService<T>的ServiceLookup,不用手动一个个注入,下面给你分步讲怎么搞:
你的核心需求是避免控制器注入一堆IService<T>参数,同时能像DbContext.Set<T>那样快速获取任意IService<T>实例,Autofac的泛型注册和集合注入特性刚好能完美解决这个问题。
1. 修正ServiceLookup的实现
首先把你的ServiceLookup改成非泛型的,利用Autofac的集合注入自动获取所有IService<T>实例,再通过反射(或者更优雅的标记接口)构建类型映射字典:
方案1:用反射自动识别泛型参数(无需修改现有IService实现)
public interface IServiceLookup { IService<TMessage> Service<TMessage>() where TMessage : class, IContractDTO; } public class ServiceLookup : IServiceLookup { private readonly IDictionary<Type, object> _serviceMap; // 关键:让Autofac自动注入所有实现了IService<T>的实例 public ServiceLookup(IEnumerable<object> allServices) { _serviceMap = allServices .Select(service => { // 找出当前实例对应的IService<T>泛型接口 var serviceInterface = service.GetType() .GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IService<>)); if (serviceInterface == null) throw new ArgumentException($"服务 {service.GetType().Name} 未实现IService<T>接口"); // 提取泛型参数T的类型 var messageType = serviceInterface.GetGenericArguments()[0]; return new { MessageType = messageType, Service = service }; }) .ToDictionary(x => x.MessageType, x => x.Service); } public IService<TMessage> Service<TMessage>() where TMessage : class, IContractDTO { if (_serviceMap.TryGetValue(typeof(TMessage), out var service)) { return service as IService<TMessage>; } throw new KeyNotFoundException($"未找到注册的IService<{typeof(TMessage).Name}>"); } }
方案2:用非泛型标记接口(更类型安全,避免反射)
如果不想用反射,可以给泛型接口加一个非泛型父接口,让每个IService<T>实现类显式声明自己对应的T类型:
// 新增非泛型标记接口 public interface IService { Type MessageType { get; } } // 修改原有泛型接口继承这个非泛型接口 public interface IService<TMessage> : IService where TMessage: IContractDTO { IQueryable<TMessage> BuildProjection(); } // 每个服务实现类都要实现MessageType属性 public class EntityOneService : IService<EntityOneDTO> { public Type MessageType => typeof(EntityOneDTO); public IQueryable<EntityOneDTO> BuildProjection() { // 你的业务实现 } }
然后ServiceLookup的构造函数可以直接接收IEnumerable<IService>,省去反射步骤:
public ServiceLookup(IEnumerable<IService> allServices) { _serviceMap = allServices.ToDictionary(s => s.MessageType, s => s); }
2. Autofac容器注册配置
接下来在Autofac里配置服务注册,确保所有IService<T>都被自动扫描并注册,同时让ServiceLookup能拿到所有实例:
var builder = new ContainerBuilder(); // 1. 自动扫描并注册所有IService<T>的实现 builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()) .AsClosedTypesOf(typeof(IService<>)) // 自动匹配泛型参数,比如EntityOneService会被注册为IService<EntityOneDTO> .InstancePerLifetimeScope(); // 根据你的业务需求调整生命周期,比如InstancePerDependency // 2. 注册ServiceLookup builder.RegisterType<ServiceLookup>() .As<IServiceLookup>() .SingleInstance(); // 查找器适合单例,因为它只是做类型映射,不持有业务状态 // 3. 注册其他依赖(比如你的IControllerServiceContext) builder.RegisterType<ControllerServiceContext>() .As<IControllerServiceContext>() .InstancePerLifetimeScope(); var container = builder.Build();
这里的AsClosedTypesOf(typeof(IService<>))是关键——Autofac会自动扫描程序集中所有实现了IService<T>闭包类型的类,并将它们注册为对应的IService<T>接口,完全不需要手动逐个注册。
3. 控制器中的使用方式(和你期望的完全一致)
现在控制器只需要注入IControllerServiceContext和IServiceLookup,就能随时获取任意IService<T>实例,再也不用堆一堆构造函数参数了:
public class SomeController : BaseODataController<SomeEntityDTO> { private readonly IServiceLookup _serviceLookup; public SomeController(IControllerServiceContext context, IServiceLookup serviceLookup) : base(context) { _serviceLookup = serviceLookup; } public IActionResult SomeMethod() { // 获取EntityOneDTO的服务 var entityOneService = _serviceLookup.Service<EntityOneDTO>(); var entityOne = entityOneService.BuildProjection().FirstOrDefault(); // 获取EntityTwoDTO的服务 var entityTwoService = _serviceLookup.Service<EntityTwoDTO>(); var entityTwo = entityTwoService.BuildProjection().FirstOrDefault(); // 处理跨实体的业务逻辑 return Ok(new { EntityOne = entityOne, EntityTwo = entityTwo }); } }
关于你补充的疑问
你提到“手动注入构建列表可能是更优解,但好奇能否无需手动操作”——上面的方案完全不需要手动维护服务列表,新增IService<T>实现时,只要符合命名规范(或者在扫描范围内),Autofac会自动识别并注入到ServiceLookup中,比手动维护更健壮,也更省心。
内容的提问来源于stack exchange,提问作者Seth

