基于键的依赖注入中,服务继承父键注入同键依赖的可行性问询
基于键的依赖注入中自动继承父服务键实现同站点依赖注入的方案
问题背景
我开发的多站点API中,订单控制器需要处理不同站点的业务逻辑。目前通过手动构建<IOrderModel>字典来匹配对应站点的服务,运行正常但需要手动管理所有依赖关系。想改用**键依赖注入(Keyed DI)**简化配置,为每个站点注册对应的<IOrderModel>和<IRepository>实例,但<CommonOrderModel>需要依赖同站点(同键)的<IRepository>,希望注入时能自动使用父服务的键来获取对应依赖(比如用[FromKeyedServices(KeyedService.ParentKey)]这种方式)。已知原生DI不支持该功能,翻源码找注入点没成功,核心问题是依赖图没在工厂方法中暴露,拿不到上层服务的键,想问这个需求能不能实现。
当前实现代码
订单控制器
[ApiController] [Route("{site}/Order")] public class OrderController { private readonly Dictionary<string, IOrderModel> _orderModels; public OrderController(Dictionary<string, IOrderModel> orderModels) { _orderModels = orderModels; } [HttpGet("{orderNumber:int}")] public async Task<IActionResult> GetOrderAsync(string site, int orderNumber) { if (!_orderModels.TryGetValue(site, out var orderModel)) return NotFound(); if (!User.HasAccessToSite(site)) return Forbid(); var order = await orderModel.GetExistingOrderAsync(orderNumber); return Ok(order); } }
手动创建服务字典的方法
private static Dictionary<string, IOrderModel> CreateOrderModels(IServiceProvider services) { var orderModels = new Dictionary<string, IOrderModel>(); var site1Repository = services.GetRequiredService<Site1Repository>(); var logger = services.GetRequiredService<ILogger<CommonOrderModel>>(); orderModels.Add("Site1", new CommonOrderModel(site1Repository, logger)); // 其他站点类似配置... return orderModels; }
期望的键DI配置及构造函数
服务注册配置
services.AddKeyedSingleton<IOrderModel, CommonOrderModel>("Site1"); services.AddKeyedSingleton<IOrderModel, SpecialSite2OrderModel>("Site2"); services.AddKeyedSingleton<IOrderModel, CommonOrderModel>("Site3"); services.AddKeyedSingleton<IRepository, Site1Repository>("Site1"); services.AddKeyedSingleton<IRepository, Site2Repository>("Site2"); services.AddKeyedSingleton<IRepository, Site3Repository>("Site3");
期望的CommonOrderModel构造函数
public CommonOrderModel([FromKeyedServices(KeyedService.ParentKey)] IRepository repository, ILogger<CommonOrderModel> logger)
实现方案
原生.NET Keyed DI确实不支持自动继承父服务键的功能,但可以通过以下两种方式实现需求:
方法1:自定义工厂注册时绑定同键依赖
注册<IOrderModel>时,使用工厂方法显式获取同键的<IRepository>,无需修改构造函数:
services.AddKeyedSingleton<IOrderModel>("Site1", (sp, key) => new CommonOrderModel(sp.GetRequiredKeyedService<IRepository>(key), sp.GetRequiredService<ILogger<CommonOrderModel>>())); services.AddKeyedSingleton<IOrderModel>("Site2", (sp, key) => new SpecialSite2OrderModel(sp.GetRequiredKeyedService<IRepository>(key), sp.GetRequiredService<ILogger<SpecialSite2OrderModel>>())); services.AddKeyedSingleton<IOrderModel>("Site3", (sp, key) => new CommonOrderModel(sp.GetRequiredKeyedService<IRepository>(key), sp.GetRequiredService<ILogger<CommonOrderModel>>()));
这种方式直接在注册阶段把同键依赖注入,逻辑清晰,无需额外扩展DI容器。
方法2:扩展DI容器实现键继承功能
如果需要批量处理或更贴近你期望的ParentKey写法,可以自定义DI扩展,通过替换服务注册的工厂逻辑,在解析依赖时传递父服务的键:
- 定义自定义属性标记需要继承父键的依赖:
[AttributeUsage(AttributeTargets.Parameter)] public class FromParentKeyedServicesAttribute : Attribute { }
- 创建自定义服务工厂,解析时跟踪当前键并注入同键依赖:
public static class KeyedServiceExtensions { public static IServiceCollection AddKeyedServiceWithParentInheritance<TService, TImplementation>(this IServiceCollection services, object key) where TService : class where TImplementation : class, TService { services.AddKeyedSingleton<TService>(key, (sp, parentKey) => { var ctor = typeof(TImplementation).GetConstructors().First(); var parameters = new List<object>(); foreach (var param in ctor.GetParameters()) { if (param.GetCustomAttribute<FromParentKeyedServicesAttribute>() != null) { parameters.Add(sp.GetRequiredKeyedService(param.ParameterType, parentKey)); } else if (param.GetCustomAttribute<FromKeyedServicesAttribute>() is FromKeyedServicesAttribute keyAttr) { parameters.Add(sp.GetRequiredKeyedService(param.ParameterType, keyAttr.Key)); } else { parameters.Add(sp.GetRequiredService(param.ParameterType)); } } return (TService)Activator.CreateInstance(typeof(TImplementation), parameters.ToArray()); }); return services; } }
- 修改
CommonOrderModel构造函数:
public CommonOrderModel([FromParentKeyedServices] IRepository repository, ILogger<CommonOrderModel> logger) { // 业务逻辑 }
- 使用扩展方法注册服务:
services.AddKeyedServiceWithParentInheritance<IOrderModel, CommonOrderModel>("Site1"); services.AddKeyedServiceWithParentInheritance<IOrderModel, SpecialSite2OrderModel>("Site2"); services.AddKeyedServiceWithParentInheritance<IOrderModel, CommonOrderModel>("Site3"); services.AddKeyedSingleton<IRepository, Site1Repository>("Site1"); services.AddKeyedSingleton<IRepository, Site2Repository>("Site2"); services.AddKeyedSingleton<IRepository, Site3Repository>("Site3");
优化控制器服务获取方式
改用Keyed DI后,控制器无需依赖整个字典,可直接通过IServiceProvider按需获取对应站点的服务:
[ApiController] [Route("{site}/Order")] public class OrderController { private readonly IServiceProvider _serviceProvider; public OrderController(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } [HttpGet("{orderNumber:int}")] public async Task<IActionResult> GetOrderAsync(string site, int orderNumber) { if (!_serviceProvider.TryGetKeyedService<IOrderModel>(site, out var orderModel)) return NotFound(); if (!User.HasAccessToSite(site)) return Forbid(); var order = await orderModel.GetExistingOrderAsync(orderNumber); return Ok(order); } }
内容的提问来源于stack exchange,提问作者adrianm
相关产品推荐
相关产品推荐

