构造函数注入的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
相关产品推荐
相关产品推荐

