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

构造函数注入的IServiceProvider与作用域IServiceProvider为何实例不一致?

问题:构造函数注入的IServiceProvider为根作用域,导致实例不一致

尝试从作用域内解析类对象时,发现构造函数注入的IServiceProvider属于根作用域,导致解析出的对象实例与当前作用域的实例不同。是否应该在BarHandlerResolver中注入其他对象而非IServiceProvider?

代码详情

using Microsoft.Extensions.DependencyInjection;

namespace TestProjectDI;

public class UnitTest1
{
    [Fact]
    public void Test1()
    {
        var services = new ServiceCollection();

        services.AddSingleton<BarHandlerResolver>();

        services.AddTransient<BarHandler>();

        services.AddScoped<ScopeFoo>();


        using var serviceProvider = services.BuildServiceProvider();
        using (var scope = serviceProvider.CreateScope())
        {
            var sp = scope.ServiceProvider;

            var tt = sp.GetType();

            var scopeFoo = sp.GetService<ScopeFoo>();

            scopeFoo.UID = Guid.NewGuid();

            var handlerResolver = sp.GetRequiredService<BarHandlerResolver>();

            // 断言失败:构造函数注入的SP是根作用域的
            Assert.Equal(scopeFoo.UID, handlerResolver.GetHandlerInstanceFromSP_Ctor().ScopeFoo.UID); 
            // 断言成功:传入的是当前作用域的SP
            Assert.Equal(scopeFoo.UID, handlerResolver.GetHandlerInstanceFromSP_Param(sp).ScopeFoo.UID); 
        }
    }
}

public class BarHandlerResolver
{
    private readonly IServiceProvider serviceProvider;

    public BarHandlerResolver(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    public BarHandler GetHandlerInstanceFromSP_Ctor()
    {
        return serviceProvider.GetService<BarHandler>();
    }

    public BarHandler GetHandlerInstanceFromSP_Param(IServiceProvider serviceProviderFromParent)
    {
        return serviceProviderFromParent.GetService<BarHandler>();
    }
}

public class BarHandler
{
    public BarHandler(ScopeFoo scopeFoo)
    {
        ScopeFoo = scopeFoo;
    }

    public ScopeFoo ScopeFoo { get; }
}

public class ScopeFoo
{
    public Guid UID { get; set; }
}

原因分析

  • BarHandlerResolver被注册为单例(Singleton)服务,单例服务从根容器解析创建,因此它构造函数注入的IServiceProvider是根容器实例,而非后续创建的子作用域实例。
  • 用根容器的IServiceProvider解析Scoped生命周期的ScopeFoo时,会在根容器中生成一个独立的ScopeFoo实例,和子作用域内的ScopeFoo并非同一对象,所以第一个断言失败。
  • 第二个方法传入了子作用域的ServiceProvider,解析时会复用当前子作用域的ScopeFoo实例,因此断言成功。

解决方案:避免注入IServiceProvider到单例服务

直接注入IServiceProvider属于服务定位器模式,是反模式,容易引发作用域问题。推荐以下两种更合理的方式:

1. 注入Func<BarHandler>工厂

利用.NET DI自动支持的Func<T>工厂,直接注入创建BarHandler的委托,无需手动操作IServiceProvider:

public class BarHandlerResolver
{
    private readonly Func<BarHandler> _barHandlerFactory;

    public BarHandlerResolver(Func<BarHandler> barHandlerFactory)
    {
        _barHandlerFactory = barHandlerFactory;
    }

    public BarHandler GetHandlerInstance()
    {
        return _barHandlerFactory();
    }
}
  • 服务注册无需修改,.NET DI会自动为Func<BarHandler>生成实现。
  • 在子作用域内调用GetHandlerInstance时,Func<BarHandler>会自动使用当前子作用域的ServiceProvider解析BarHandler,从而获取到当前作用域的ScopeFoo实例,断言会成功。

2. 注入IServiceScopeFactory(适合手动管理作用域的场景)

如果需要在单例服务中主动创建或使用作用域,可以注入IServiceScopeFactory:

public class BarHandlerResolver
{
    private readonly IServiceScopeFactory _scopeFactory;

    public BarHandlerResolver(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public BarHandler GetHandlerInstanceInNewScope()
    {
        using var scope = _scopeFactory.CreateScope();
        return scope.ServiceProvider.GetService<BarHandler>();
    }
}
  • 这种方式适合在单例中独立创建新作用域的场景,但如果要复用当前调用方的作用域,优先推荐第一种Func<T>的方式。

3. 调整服务生命周期(可选)

如果业务允许,可以将BarHandlerResolver改为Scoped生命周期:

services.AddScoped<BarHandlerResolver>();
  • 这样每次创建子作用域时,都会生成新的BarHandlerResolver实例,它注入的IServiceProvider就是当前子作用域的实例,也能解决问题。但如果业务要求BarHandlerResolver必须是单例,这种方式不适用。

内容的提问来源于stack exchange,提问作者Vincent Dagpin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:44:54