.NET Core DI允许将作用域服务注入到本地机器上的单例中。

8

我在 Startup.cs 文件中有以下代码:

    public void ConfigureServices(IServiceCollection services)
    {
        // EF
        services.AddDbContext<MyDatabaseContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        // domain services            
        services.AddScoped<ILookupService, LookupService>();
        
        services.AddMemoryCache();

        // singleton
        services.AddSingleton<CacheManager>();            
    }


public class CacheManager
{
    private readonly ILookupService _lookupService;
    private readonly IMemoryCache _cache;
    public CacheManager(ILookupService lookupService, IMemoryCache cache)
    {
        _lookupService = lookupService;
        _cache = cache;
    }
}

当我部署应用程序在开发服务器上并尝试访问时,我看到异常

AggregateException:一些服务无法构建 (验证服务描述符时出错:'ServiceType: UI.Helpers.CacheManager Lifetime: Singleton ImplementationType: UI.Helpers.CacheManager':无法从单例 'UI.Helpers.CacheManager' 使用作用域服务 'ApplicationCore.Services.ILookupService'。)

请注意,我理解为什么会出现这种情况,并将予以修正。基本上,您不能将范围较小的服务注入到范围较大的服务中。ASP.NET Core的DI系统会尝试确保您不这样做。

我不明白的是,在本地运行应用程序时为什么没有看到此问题?


1
我想这一定是一个环境或运行时设置的问题。它在你拥有的另一台机器上工作吗?还是错误弹出在云端/数据中心? - Ben Matthews
3个回答

2
今天我在自动寻找麻烦的时候遇到了这个问题。
根据文档WebHost.CreateDefaultBuilder 的实现仅在环境为Development 的情况下将 ServiceProviderOptions.ValidateScopes 设置为 true. 默认情况下,如果以下任何一个环境变量设置为 DevelopmentASPNETCORE_ENVIRONMENTDOTNET_ENVIRONMENT。 我在使用 Host.CreateDefaultBuilder 时也遇到了同样的行为。
当将ServiceProviderOptions.ValidateScopes设置为false时,将一个作用域服务注入到单例服务中不会导致运行时错误,而是使用某种瞬态行为解析依赖项。
我只能想象这个设置存在是为了避免在更高的环境中执行这些验证的开销。在开发时执行此操作对我来说已经足够了。
只是为了重申@LP13已经说明的内容。将作用域服务注入到单例服务中是无效的。为此,您将不得不在单例实例内创建作用域
我引用了.NET Core 3.1文档,但这在.NET 6中仍然有效。 作用域验证文档

0
要在单例中使用作用域服务,必须手动创建作用域。可以通过将IServiceScopeFactory注入到单例服务中来创建新的作用域(IServiceScopeFactory本身是一个单例,这就是为什么它能够工作的原因)。IServiceScopeFactory具有CreateScope方法,用于创建新的作用域实例。
public class MySingletonService
{
  private readonly IServiceScopeFactory _serviceScopeFactory;

  public MySingletonService(IServiceScopeFactory serviceScopeFactory)
  {
    _serviceScopeFactory = serviceScopeFactory;
  }

  public void Execute()
  {
    using (var scope = _serviceScopeFactory.CreateScope())
    {
      var myScopedService = scope.ServiceProvider.GetService<IMyScopedService>();    
      myScopedService.DoSomething();
    }
  }
}

创建的范围具有自己的 IServiceProvider,你可以访问它来解析您的作用域服务。
确保范围仅存在所需的时间,并在完成使用后正确处理它非常重要。这是为避免任何俘获依赖性问题。因此,建议:
  • 仅在打算使用它的方法内定义该范围。将其分配给其他地方的单例服务进行重用可能很诱人,但这也会导致俘获依赖项。
  • 将范围包装在 using 语句中。这将确保一旦完成使用,范围就会被正确处理掉。

0

MSDN提供了解决此问题的方案。

在BackgroundService中使用作用域服务

因此,在您的单例服务器(CacheManager)中只需使用using (IServiceScope scope = _serviceProvider.CreateScope())

您的CacheManager将是这样的:

public class CacheManager
{
    private readonly IMemoryCache _cache;
    private readonly IServiceProvider _serviceProvider;

    public CacheManager( IMemoryCache cache
        , IServiceProvider serviceProvider)
    {
        _cache = cache;
        _serviceProvider = serviceProvider;
    }

    public void Execute()
    {
        using (IServiceScope scope = _serviceProvider.CreateScope())
        {
            var _lookupService =
                scope.ServiceProvider.GetRequiredService<ILookupService>();

            //... do what you want
        }

    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接