在.NET 8中基于消费者服务实现带命名IOptions的依赖注入方案
嘿,你这个场景其实正好对应.NET 8刚引入的**键控服务(Keyed Services)**特性,完美解决你需要给不同消费者注入不同配置变体服务的需求!我来一步步给你捋清楚怎么做:
核心思路
你已经搞定了命名IOptions的绑定,现在的问题是要让不同的消费者服务拿到对应配置的ServiceWithOptions实例。因为默认DI注册的服务是单一实例,所以我们需要给每个配置变体创建独立的ServiceWithOptions实例,并让消费者能精准获取到对应实例。
方案一:用.NET 8键控服务(推荐)
这是最优雅的方式,正好利用.NET 8的新特性,完全贴合你的需求:
1. 注册带键的IServiceWithOptions实例
把你原来的services.AddSingleton<IServiceWithOptions, ServiceWithOptions>()替换为按键注册的版本,每个键对应你之前配置的命名IOptions:
// 为First配置对应的ServiceWithOptions注册,键为"First" services.AddKeyedSingleton<IServiceWithOptions, ServiceWithOptions>("First", (sp, key) => new ServiceWithOptions(sp.GetRequiredService<IOptions<MyOptions>>(key))); // 为SecondPrimary配置注册,键为"SecondPrimary" services.AddKeyedSingleton<IServiceWithOptions, ServiceWithOptions>("SecondPrimary", (sp, key) => new ServiceWithOptions(sp.GetRequiredService<IOptions<MyOptions>>(key))); // 为SecondSecondary配置注册,键为"SecondSecondary" services.AddKeyedSingleton<IServiceWithOptions, ServiceWithOptions>("SecondSecondary", (sp, key) => new ServiceWithOptions(sp.GetRequiredService<IOptions<MyOptions>>(key)));
这里的key参数直接对应你注册的命名IOptions名称,通过sp.GetRequiredService<IOptions<MyOptions>>(key)就能拿到对应配置的选项实例,用来构造ServiceWithOptions。
2. 在消费者服务中指定注入的键
修改消费者的构造器,用[FromKeyedServices]特性标记要注入的服务键,就能精准获取对应实例:
FirstService(只需要一个实例)
public class FirstService : IFirstService { public FirstService([FromKeyedServices("First")] IServiceWithOptions svcWithOpts) { // 这里的svcWithOpts就是使用"MyOptions:First"配置的实例 } }
SecondService(需要两个不同实例)
public class SecondService : ISecondService { public SecondService( [FromKeyedServices("SecondPrimary")] IServiceWithOptions svcWithOptsPrimary, [FromKeyedServices("SecondSecondary")] IServiceWithOptions svcWithOptsSecondary) { // 两个实例分别对应"MyOptions:SecondPrimary"和"MyOptions:SecondSecondary"配置 } }
3. 保留你的命名Options注册
你原来的命名Options绑定代码是正确的,直接保留即可:
services.AddOptions<MyOptions>("First").Bind(configuration.GetSection("MyOptions:First")); services.AddOptions<MyOptions>("SecondPrimary").Bind(configuration.GetSection("MyOptions:SecondPrimary")); services.AddOptions<MyOptions>("SecondSecondary").Bind(configuration.GetSection("MyOptions:SecondSecondary"));
方案二:用工厂模式(兼容旧版本)
如果因为某些原因不想用键控服务,也可以通过注册工厂的方式实现:
1. 注册服务工厂
添加一个Func<string, IServiceWithOptions>的工厂注册,用来根据键名生成对应实例:
services.AddSingleton<Func<string, IServiceWithOptions>>(sp => key => new ServiceWithOptions(sp.GetRequiredService<IOptions<MyOptions>>(key)));
2. 消费者通过工厂获取实例
修改消费者构造器,注入这个工厂,然后通过键名获取对应实例:
public class SecondService : ISecondService { private readonly IServiceWithOptions _primarySvc; private readonly IServiceWithOptions _secondarySvc; public SecondService(Func<string, IServiceWithOptions> svcFactory) { _primarySvc = svcFactory("SecondPrimary"); _secondarySvc = svcFactory("SecondSecondary"); } }
这种方式的缺点是消费者需要知道具体的键名,耦合性略高,不如键控服务优雅。
注意事项
- 所有服务都可以注册为单例,完全符合你的需求,每个键对应的
ServiceWithOptions都是单一实例。 - 一定要删除原来的
services.AddSingleton<IServiceWithOptions, ServiceWithOptions>(),否则DI容器会存在多个同类型服务,导致注入时出错。
内容来源于stack exchange

