Asp.Net Core 7依赖注入:多实例注入与不同IOptions解析方案
问题描述
现有代码:
public interface ITestService{}
public class TestService : ITestService { public TestService(IOptions<TestOptions> testOptions){ // do something with testOptions.value } }
appsettings.json配置:
{ "TestOptions1" : { "Url": "something" }, "TestOptions2" : { "Url": "something" } }
需求:
在控制器中注入不同的ITestService实例,使其分别使用appsettings.json中的TestOptions1或TestOptions2配置。理想状态是能直接获取对应配置的服务实例。
补充说明:项目中已有大量控制器使用ITestService,希望新增控制器能使用注入不同IOptions的同类型ITestService,尽可能少改动现有代码(最好不改动)。
解决方案
.NET 8+ 推荐方案:键服务(Keyed Services)
.NET 8原生支持键服务,无需修改现有服务类,直接通过键区分不同配置的实例:
1. 注册配置与带键服务
在Program.cs中添加以下注册代码:
// 注册两个命名配置节,绑定到TestOptions builder.Services.Configure<TestOptions>("TestOptions1", builder.Configuration.GetSection("TestOptions1")); builder.Services.Configure<TestOptions>("TestOptions2", builder.Configuration.GetSection("TestOptions2")); // 注册带键的ITestService实例,根据键获取对应配置 builder.Services.AddKeyedTransient<ITestService, TestService>("TestOptions1", (sp, key) => new TestService(sp.GetRequiredService<IOptionsMonitor<TestOptions>>().Get(key.ToString())) ); builder.Services.AddKeyedTransient<ITestService, TestService>("TestOptions2", (sp, key) => new TestService(sp.GetRequiredService<IOptionsMonitor<TestOptions>>().Get(key.ToString())) ); // 保留原有默认注册,兼容已使用ITestService的控制器 builder.Services.AddTransient<ITestService, TestService>(sp => new TestService(sp.GetRequiredService<IOptions<TestOptions>>()) );
2. 控制器中使用
- 原有控制器:无需任何修改,继续正常注入
ITestService即可:
public class OldController : ControllerBase { private readonly ITestService _testService; public OldController(ITestService testService) { _testService = testService; } }
- 新增控制器:通过
[FromKeyedServices]特性指定键名,直接注入对应实例:
public class NewController : ControllerBase { private readonly ITestService _serviceWithOpt1; private readonly ITestService _serviceWithOpt2; public NewController( [FromKeyedServices("TestOptions1")] ITestService serviceWithOpt1, [FromKeyedServices("TestOptions2")] ITestService serviceWithOpt2 ) { _serviceWithOpt1 = serviceWithOpt1; _serviceWithOpt2 = serviceWithOpt2; } }
兼容旧版本.NET(.NET 6及以下)
如果项目基于.NET 6或更早版本,用工厂模式实现,同样无需修改现有服务类:
1. 注册配置与服务工厂
在Program.cs中添加:
// 注册两个命名配置节 builder.Services.Configure<TestOptions>("TestOptions1", builder.Configuration.GetSection("TestOptions1")); builder.Services.Configure<TestOptions>("TestOptions2", builder.Configuration.GetSection("TestOptions2")); // 注册一个工厂委托,根据键名创建对应配置的TestService实例 builder.Services.AddTransient<Func<string, ITestService>>(sp => key => { var optionsMonitor = sp.GetRequiredService<IOptionsMonitor<TestOptions>>(); return new TestService(optionsMonitor.Get(key)); }); // 保留原有默认注册,兼容旧控制器 builder.Services.AddTransient<ITestService, TestService>(sp => new TestService(sp.GetRequiredService<IOptions<TestOptions>>()) );
2. 控制器中使用工厂
新增控制器注入工厂委托,直接传入配置名称获取实例:
public class NewController : ControllerBase { private readonly ITestService _serviceWithOpt1; private readonly ITestService _serviceWithOpt2; public NewController(Func<string, ITestService> testServiceFactory) { _serviceWithOpt1 = testServiceFactory("TestOptions1"); _serviceWithOpt2 = testServiceFactory("TestOptions2"); } }
核心优势
- 完全无需修改
ITestService和TestService的原有代码,满足“少改动”要求。 - 原有控制器不受影响,依然可以正常使用默认的
ITestService实例。 - 新增控制器可以快速获取对应配置的服务实例,实现需求中的“直接调用”效果。
内容的提问来源于stack exchange,提问作者fijasko_ultimate
相关产品推荐
相关产品推荐

