为何HostBuilder可解析作用域服务,WebApplicationBuilder却报错?
问题解答
一、为什么HostBuilder能实例化?
在.NET 6之前的HostBuilder实现中,根服务提供者默认允许直接解析作用域服务(这其实是不符合DI设计原则的不规范实现)——作用域服务本该在专属的作用域内创建,直接从根容器获取会把它的生命周期强行提升为单例,可能引发资源泄漏或并发问题。而WebApplicationBuilder(.NET 6+)修复了这个漏洞,严格遵循DI规则:根服务提供者无法解析作用域服务,以此避免生命周期混乱,所以会抛出异常。
二、WebApplicationBuilder下的正确实现方式
正确的做法是让后台服务通过IServiceScopeFactory或IServiceProvider创建作用域,再在作用域内解析Transporter,绝对不能直接从根容器获取作用域服务。推荐用IServiceScopeFactory,它是专门用于创建作用域的服务,更符合DI最佳实践:
方式1:注入IServiceScopeFactory(推荐)
修改MyBackgroundService的构造函数,注入IServiceScopeFactory和单例的Config,然后在需要使用Transporter时创建作用域:
public class MyBackgroundService : BackgroundService { private readonly Config _config; private readonly IServiceScopeFactory _scopeFactory; public MyBackgroundService(Config config, IServiceScopeFactory scopeFactory) { _config = config; _scopeFactory = scopeFactory; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // 创建作用域,作用域内的服务会在using块结束后自动释放 using var scope = _scopeFactory.CreateScope(); var transporter = scope.ServiceProvider.GetRequiredService<Transporter>(); // 执行业务逻辑 await transporter.DoWorkAsync(stoppingToken); await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); } } }
服务注册时按常规方式添加即可:
builder.Services.AddSingleton<Config>(); builder.Services.AddScoped<Transporter>(); builder.Services.AddHostedService<MyBackgroundService>();
方式2:注入IServiceProvider(可行但不推荐)
如果一定要用IServiceProvider,也可以通过它创建作用域,但不如IServiceScopeFactory语义明确:
public class MyBackgroundService : BackgroundService { private readonly Config _config; private readonly IServiceProvider _serviceProvider; public MyBackgroundService(Config config, IServiceProvider serviceProvider) { _config = config; _serviceProvider = serviceProvider; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using var scope = _serviceProvider.CreateScope(); var transporter = scope.ServiceProvider.GetRequiredService<Transporter>(); // 执行业务逻辑 await transporter.DoWorkAsync(stoppingToken); await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); } } }
关键注意点
- 禁止从根服务提供者直接解析作用域服务,这会导致服务实例被长期持有,无法正确释放,还可能引发并发问题。
- 后台服务是单例生命周期,所以注入的服务要么是单例,要么是能创建作用域的服务(如
IServiceScopeFactory)。
内容的提问来源于stack exchange,提问作者Birdalicious
相关产品推荐
相关产品推荐

