.NET中多配置作用域ICustomService实例的优化实现方案问询
针对多配置Scoped服务的DI优化方案
问题分析
当前两种方案的核心痛点:
- 直接注册多个
ICustomService实例:每次请求都要遍历筛选,且Scoped请求会初始化所有实例,造成HttpClient/WCF客户端这类资源的不必要消耗。 - 自定义工厂模式:需要手动管理服务生命周期(实现
IAsyncDisposable清理),和.NET原生DI的集成不够顺畅,冗余代码多。
更优实现方案
方案1:键控服务映射+Resolver(避免遍历,原生DI管理生命周期)
通过包装类绑定服务与 jurisdiction 标识,提前建立映射,避免每次请求遍历筛选:
1. 定义键控包装类
public class KeyedCustomService { public string Jurisdiction { get; } public ICustomService Service { get; } public KeyedCustomService(string jurisdiction, ICustomService service) { Jurisdiction = jurisdiction; Service = service; } }
2. 注册时绑定标识与服务
// Startup/Program.cs // 先注册对应jurisdiction的具体实现 services.AddScoped<UKCustomService>(); services.AddScoped<USCustomService>(); services.AddScoped<AUCustomService>(); // 包装成键控服务注册 services.AddScoped<KeyedCustomService>(sp => new KeyedCustomService("UK", sp.GetRequiredService<UKCustomService>())); services.AddScoped<KeyedCustomService>(sp => new KeyedCustomService("US", sp.GetRequiredService<USCustomService>())); services.AddScoped<KeyedCustomService>(sp => new KeyedCustomService("AU", sp.GetRequiredService<AUCustomService>()));
注:UKCustomService等是ICustomService的具体实现,各自对应不同配置
3. 注册Resolver实现快速检索
public interface ICustomServiceResolver { ICustomService GetByJurisdiction(string jurisdiction); } public class CustomServiceResolver : ICustomServiceResolver { private readonly Dictionary<string, ICustomService> _serviceMap; public CustomServiceResolver(IEnumerable<KeyedCustomService> services) { _serviceMap = services.ToDictionary(s => s.Jurisdiction, s => s.Service); } public ICustomService GetByJurisdiction(string jurisdiction) { if (_serviceMap.TryGetValue(jurisdiction, out var service)) return service; throw new InvalidOperationException($"未找到对应{jurisdiction}的服务实例"); } } // 注册Resolver到DI services.AddScoped<ICustomServiceResolver, CustomServiceResolver>();
4. API中直接使用
public async Task<ThingResult> DoThing([FromServices] ICustomServiceResolver resolver) { var jurisdiction = await GetCustomer(...).Juristriction.Key; // UK | US | AU var service = resolver.GetByJurisdiction(jurisdiction); return await service.DoThing(...); }
优势:Resolver初始化时仅建立一次映射,避免重复遍历;所有服务生命周期由原生DI管理,无需手动处理Dispose。
方案2:Func工厂+按需创建(减少资源开销)
如果不想提前初始化所有Scoped实例(比如HttpClient创建成本高),可以用Func<string, ICustomService>结合DI实现延迟创建:
1. 注册配置与工厂方法
// 注册各jurisdiction的配置(用IOptions管理) services.Configure<CustomServiceConfig>("UK", config => { // 填充UK相关配置 }); services.Configure<CustomServiceConfig>("US", config => { // 填充US相关配置 }); services.Configure<CustomServiceConfig>("AU", config => { // 填充AU相关配置 }); // 注册Func工厂,按需创建服务 services.AddTransient<Func<string, ICustomService>>(sp => jurisdiction => { var config = sp.GetRequiredService<IOptionsMonitor<CustomServiceConfig>>().Get(jurisdiction); // 根据配置创建ICustomService实例(假设CustomService是通用实现) return new CustomService(config); });
2. API中使用并管理生命周期
public async Task<ThingResult> DoThing([FromServices] Func<string, ICustomService> serviceFactory) { var jurisdiction = await GetCustomer(...).Juristriction.Key; // 如果服务实现了IAsyncDisposable,用using自动清理 using var service = serviceFactory(jurisdiction) as IAsyncDisposable; return await service.DoThing(...); }
优势:仅在需要时创建服务实例,避免Scoped请求中初始化所有实例的资源浪费;无需自定义工厂接口,和原生DI集成更自然。
方案3:利用第三方DI库的原生命名服务支持
如果你已经在用Autofac、DryIoc这类第三方DI库,它们本身支持命名/键控服务,直接开箱即用:
以Autofac为例:
// 注册时指定服务名称 builder.RegisterType<UKCustomService>().As<ICustomService>().Named<ICustomService>("UK"); builder.RegisterType<USCustomService>().As<ICustomService>().Named<ICustomService>("US"); builder.RegisterType<AUCustomService>().As<ICustomService>().Named<ICustomService>("AU"); // 注册Resolver builder.RegisterType<CustomServiceResolver>().As<ICustomServiceResolver>(); // Resolver中通过名称获取服务 public class CustomServiceResolver : ICustomServiceResolver { private readonly IComponentContext _context; public CustomServiceResolver(IComponentContext context) { _context = context; } public ICustomService GetByJurisdiction(string jurisdiction) { return _context.ResolveNamed<ICustomService>(jurisdiction); } }
优势:无需自己实现包装类和映射逻辑,直接用第三方库的原生能力简化开发。
总结
- 要避免遍历+原生DI管理生命周期:选方案1
- 要延迟创建、降低资源开销:选方案2
- 已使用第三方DI库:直接用其原生命名服务支持
内容的提问来源于stack exchange,提问作者Keith
相关产品推荐
相关产品推荐

