自定义工厂构建含运行时参数的类:如何通过.NET Core DI维护容器生存期?
如何让含运行时构造参数的类实例仍由Microsoft DI管控生存期?
当类的构造函数依赖仅能在运行时获取的参数时,常规自定义工厂会完全掌控实例创建,导致容器无法管控其生存期(如Scoped、Singleton)。以下是解决思路和具体实现:
问题场景回顾
我们有依赖运行时参数的服务类:
interface IFooService { void DoServicyStuff(); } class MyFooService : IFooService { private readonly string _peskyValue; public MyFooService(string somePeskyRuntimeArgument) { _peskyValue = somePeskyRuntimeArgument; } public void DoServicyStuff() { // 使用_peskyValue执行逻辑 } }
常规工厂直接new实例的方式会脱离容器管控,而用ActivatorUtilities创建实例时,容器会忽略已注册的生存期策略,每次调用都生成新实例。
解决方案一:带作用域缓存的工厂(适配Scoped生存期)
通过在工厂内维护当前作用域的实例缓存,确保同一作用域(如同一请求)内相同参数仅创建一次实例,同时利用ActivatorUtilities自动注入其他容器依赖。
实现代码
interface IFooServiceFactory { IFooService CreateService(string runtimeParam); } class FooServiceFactory : IFooServiceFactory { private readonly IServiceProvider _serviceProvider; private readonly Dictionary<string, IFooService> _scopedInstanceCache = new(); public FooServiceFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IFooService CreateService(string runtimeParam) { // 优先从当前作用域缓存获取实例 if (_scopedInstanceCache.TryGetValue(runtimeParam, out var existingInstance)) { return existingInstance; } // 用ActivatorUtilities创建实例,自动注入容器内的其他依赖 var newInstance = ActivatorUtilities.CreateInstance<MyFooService>(_serviceProvider, runtimeParam); _scopedInstanceCache[runtimeParam] = newInstance; return newInstance; } }
注册方式
将工厂注册为Scoped,确保每个作用域拥有独立的缓存:
services.AddScoped<IFooServiceFactory, FooServiceFactory>();
解决方案二:全局缓存工厂(适配Singleton生存期)
如果需要同一参数的实例全局唯一,可使用线程安全的全局缓存,配合Singleton工厂实现:
实现代码
class FooServiceFactory : IFooServiceFactory { private readonly IServiceProvider _serviceProvider; private static readonly ConcurrentDictionary<string, IFooService> _singletonInstanceCache = new(); public FooServiceFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IFooService CreateService(string runtimeParam) { // 线程安全的全局缓存,确保同一参数仅创建一次实例 return _singletonInstanceCache.GetOrAdd(runtimeParam, param => ActivatorUtilities.CreateInstance<MyFooService>(_serviceProvider, param)); } }
注册方式
将工厂注册为Singleton:
services.AddSingleton<IFooServiceFactory, FooServiceFactory>();
解决方案三:参数包装为Scoped服务(适配单一参数场景)
如果同一作用域内参数值固定(如每个请求仅对应一个参数),可将运行时参数包装为Scoped服务,直接通过容器解析IFooService,完全由容器管控生存期。
实现步骤
- 创建参数包装类:
public class FooServiceRuntimeParam { public string Value { get; set; } }
- 修改
MyFooService构造函数:
class MyFooService : IFooService { private readonly string _peskyValue; public MyFooService(FooServiceRuntimeParam runtimeParam) { _peskyValue = runtimeParam.Value; } public void DoServicyStuff() { // 使用_peskyValue执行逻辑 } }
- 注册服务并在请求开始时设置参数:
// 注册Scoped参数服务和FooService services.AddScoped<FooServiceRuntimeParam>(); services.AddScoped<IFooService, MyFooService>(); // 在请求管道中设置参数 app.Use(async (context, next) => { // 从请求中获取运行时参数(示例从QueryString获取) var paramValue = context.Request.Query["param"].ToString(); var scopedParam = context.RequestServices.GetRequiredService<FooServiceRuntimeParam>(); scopedParam.Value = paramValue; await next(); });
关键说明
ActivatorUtilities.CreateInstance会自动从容器中解析类的其他构造依赖,仅需传入运行时参数。- 缓存逻辑需匹配目标生存期:Scoped用作用域内的字典,Singleton用线程安全的全局字典。
- 参数包装方式仅适用于同一作用域内参数唯一的场景,优势是完全依托容器原生的生存期管理。
内容的提问来源于stack exchange,提问作者julealgon
相关产品推荐
相关产品推荐

