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

.NET 8键控服务中如何为注入的IOptions配置键映射?

.NET 8 键控服务与 IOptions 实例按键映射的解决方案

问题分析

.NET 8 引入的键控服务允许通过 [FromKeyedServices] 特性在构造函数中注入同一接口的不同实例,适配物理设备这类多实例场景,但目前存在局限:无法直接为键控服务关联对应键的 IOptions 实例,导致同类型键控服务共享同一份配置(比如三个 Motor 实例均获取X轴配置)。手动逐个创建绑定 Options 实例繁琐,且构造函数含其他依赖时维护成本更高。

优雅解决方案

1. 自定义键控 Options 工厂

实现 IKeyedServiceFactory 工厂类,根据键生成对应 IOptions<T> 实例并绑定配置节点:

public class KeyedOptionsFactory<T> : IKeyedServiceFactory
    where T : class, new()
{
    private readonly IConfiguration _configuration;
    private readonly IOptionsMonitorCache<T> _cache;

    public KeyedOptionsFactory(IConfiguration configuration, IOptionsMonitorCache<T> cache)
    {
        _configuration = configuration;
        _cache = cache;
    }

    public object Create(IServiceProvider serviceProvider, object serviceKey)
    {
        var key = serviceKey.ToString();
        var options = new T();
        _configuration.GetSection($"Motors:{key}").Bind(options);
        
        // 缓存实例避免重复创建
        _cache.TryAdd(key, options);
        return Options.Create(options);
    }
}

注册工厂与按键绑定的 IOptions<T>:

builder.Services.AddKeyedSingleton<IKeyedServiceFactory, KeyedOptionsFactory<MotorOptions>>("MotorOptionsFactory");

// 为X、Y、Z轴分别注册对应键的IOptions<MotorOptions>
builder.Services.AddKeyedSingleton<IOptions<MotorOptions>>(sp =>
{
    var factory = sp.GetRequiredKeyedService<IKeyedServiceFactory>("MotorOptionsFactory");
    return (IOptions<MotorOptions>)factory.Create(sp, "X");
}, "X");

builder.Services.AddKeyedSingleton<IOptions<MotorOptions>>(sp =>
{
    var factory = sp.GetRequiredKeyedService<IKeyedServiceFactory>("MotorOptionsFactory");
    return (IOptions<MotorOptions>)factory.Create(sp, "Y");
}, "Y");

builder.Services.AddKeyedSingleton<IOptions<MotorOptions>>(sp =>
{
    var factory = sp.GetRequiredKeyedService<IKeyedServiceFactory>("MotorOptionsFactory");
    return (IOptions<MotorOptions>)factory.Create(sp, "Z");
}, "Z");

在 Motor 构造函数中注入对应键的配置:

public class Motor : IMotor
{
    public Motor([FromKeyedServices("X")] IOptions<MotorOptions> options)
    {
        // 使用X轴专属配置
    }
}

2. 扩展 IServiceCollection 简化批量注册

封装扩展方法,避免重复编写注册代码:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddKeyedOptions<T>(this IServiceCollection services, params string[] keys)
        where T : class, new()
    {
        var factoryKey = $"{typeof(T).Name}Factory";
        services.AddKeyedSingleton<IKeyedServiceFactory, KeyedOptionsFactory<T>>(factoryKey);
        
        foreach (var key in keys)
        {
            services.AddKeyedSingleton<IOptions<T>>(sp =>
            {
                var factory = sp.GetRequiredKeyedService<IKeyedServiceFactory>(factoryKey);
                return (IOptions<T>)factory.Create(sp, key);
            }, key);
        }
        
        return services;
    }
}

使用时只需一行代码完成多键注册:

builder.Services.AddKeyedOptions<MotorOptions>("X", "Y", "Z");

3. 结合命名选项实现关联

利用命名选项特性,将键控服务与命名配置绑定:
首先注册命名 IOptions:

builder.Services.Configure<MotorOptions>("X", config => _configuration.GetSection("Motors:X").Bind(config));
builder.Services.Configure<MotorOptions>("Y", config => _configuration.GetSection("Motors:Y").Bind(config));
builder.Services.Configure<MotorOptions>("Z", config => _configuration.GetSection("Motors:Z").Bind(config));

创建键控服务工厂,获取对应命名配置并实例化服务:

public class KeyedMotorFactory : IKeyedServiceFactory
{
    private readonly IServiceProvider _sp;

    public KeyedMotorFactory(IServiceProvider sp)
    {
        _sp = sp;
    }

    public object Create(IServiceProvider serviceProvider, object serviceKey)
    {
        var key = serviceKey.ToString();
        var options = _sp.GetRequiredService<IOptionsMonitor<MotorOptions>>().Get(key);
        // 如需其他依赖,直接从_sp获取后传入构造函数
        return new Motor(Options.Create(options));
    }
}

最后注册键控 IMotor 实例:

builder.Services.AddKeyedSingleton<IKeyedServiceFactory, KeyedMotorFactory>("MotorFactory");
builder.Services.AddKeyedSingleton<IMotor>(sp =>
{
    var factory = sp.GetRequiredKeyedService<IKeyedServiceFactory>("MotorFactory");
    return (IMotor)factory.Create(sp, "X");
}, "X");

// 重复注册Y、Z轴对应的IMotor

场景是否超出预期?

键控服务的设计初衷包含物理设备、多实例服务这类场景,你的需求完全在预期范围内。官方未提供原生绑定支持,是因为 IOptions 体系基于命名而非键控设计,但通过上述自定义工厂或扩展方式,可优雅实现需求,无需依赖繁琐的手动绑定。

内容的提问来源于stack exchange,提问作者daedalus12

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 18:14:54