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

自定义工厂构建含运行时参数的类:如何通过.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,完全由容器管控生存期。

实现步骤

  1. 创建参数包装类:
public class FooServiceRuntimeParam
{
    public string Value { get; set; }
}
  1. 修改MyFooService构造函数:
class MyFooService : IFooService
{
    private readonly string _peskyValue;

    public MyFooService(FooServiceRuntimeParam runtimeParam)
    {
        _peskyValue = runtimeParam.Value;
    }

    public void DoServicyStuff()
    {
        // 使用_peskyValue执行逻辑
    }
}
  1. 注册服务并在请求开始时设置参数:
// 注册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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 04:54:35