You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于键的依赖注入中,服务继承父键注入同键依赖的可行性问询

基于键的依赖注入中自动继承父服务键实现同站点依赖注入的方案

问题背景

我开发的多站点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扩展,通过替换服务注册的工厂逻辑,在解析依赖时传递父服务的键:

  1. 定义自定义属性标记需要继承父键的依赖:
[AttributeUsage(AttributeTargets.Parameter)]
public class FromParentKeyedServicesAttribute : Attribute
{
}
  1. 创建自定义服务工厂,解析时跟踪当前键并注入同键依赖:
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;
    }
}
  1. 修改CommonOrderModel构造函数:
public CommonOrderModel([FromParentKeyedServices] IRepository repository, ILogger<CommonOrderModel> logger)
{
    // 业务逻辑
}
  1. 使用扩展方法注册服务:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 19:25:54