升级到ASP.NET Core 2.0后,无法从singleton IActiveUsersService使用作用域服务IMongoDbContext

我今天更新了一个项目到ASP.NET Core 2,我收到以下错误:

无法从singleton IActiveUsersService使用作用域服务IMongoDbContext

我有以下注册:

services.AddSingleton(); services.AddScoped(); services.AddSingleton(option => { var client = new MongoClient(MongoConnectionString.Settings); return client.GetDatabase(MongoConnectionString.Database); }) public class MongoDbContext : IMongoDbContext { private readonly IMongoDatabase _database; public MongoDbContext(IMongoDatabase database) { _database = database; } public IMongoCollection GetCollection() where T : Entity, new() { return _database.GetCollection(new T().CollectionName); } } public class IActiveUsersService: ActiveUsersService { public IActiveUsersService(IMongoDbContext mongoDbContext) { ... } } 

为什么DI无法使用该服务? 一切都适用于ASP.NET Core 1.1。

您不能使用寿命较短的服务。 仅按请求存在范围服务,而单一服务创建一次并且实例共享。

现在,应用程序中IActiveUsersService存在一个IActiveUsersService实例。 但是它希望依赖于Scoped的MongoDbContext ,并且是按请求创建的。

你必须要么:

a:使MongoDbContext成为Singleton

要么

b:使IActiveUsersService成为Scoped

要么

c:将MongoDbContext作为函数参数传递给用户服务

Scoped和Singleton服务之间存在重要差异。 警告是为了让它变亮,并将其关闭或不加选择地切换寿命以使其消失将无法解决问题。

范围服务是从IServiceScope创建的。 其最重要的目的之一是确保在范围本身时正确处理在该范围内创建的任何IDisposable服务。

在ASP.NET Core中,会在每个传入请求中自动为您创建服务范围,因此您通常不必担心这一点。 但是,您也可以创建自己的服务范围; 你只需要自己处理它。

一种方法是:

  • 使您的单身人士服务具有IDisposable
  • 注入IServiceProvider
  • 使用IServiceProvider.CreateScope()扩展方法创建和存储IServiceScope范围,
  • 使用该范围来创建所需的范围服务,
  • Dispose方法中配置服务范围。
 services.AddSingleton(); services.AddScoped(); services.AddSingleton(option => { var client = new MongoClient(MongoConnectionString.Settings); return client.GetDatabase(MongoConnectionString.Database); }) public class MongoDbContext : IMongoDbContext { private readonly IMongoDatabase _database; public MongoDbContext(IMongoDatabase database) { _database = database; } public IMongoCollection GetCollection() where T : Entity, new() { return _database.GetCollection(new T().CollectionName); } } public class ActiveUsersService: IActiveUsersService, IDisposable { private readonly IServiceScope _scope; public ActiveUsersService(IServiceProvider services) { _scope = services.CreateScope(); // CreateScope is in Microsoft.Extensions.DependencyInjection } public IEnumerable GetFooData() { using (var context = _scope.ServiceProvider.GetRequiredService()) { return context.GetCollection(); } } public void Dispose() { _scope?.Dispose(); } } 

根据您使用这些以及您正在使用的作用域服务的方式,您可以改为执行以下操作之一:

  • 创建作用域服务的单个实例,并将其用于单例的生命周期; 要么
  • 存储对(注入的)根IServiceProvider的引用,每次需要一个作用域服务时,使用它在using块中创建一个新的IServiceScope ,并在块退出时让范围被释放。

请记住,任何从IServiceScope创建的IDisposable服务都会在范围本身时自动处理。

简而言之,不要只是改变服务的生命周期来“让它工作”; 你仍然需要考虑这些并确保它们得到妥善处理。 ASP.NET Core自动处理最常见的情况; 对于其他人来说,你只需要做更多的工作。

自从C#1.0以来,我们已经using()using()块来确保正确处理资源。 但是当其他东西(DI服务)为您创建这些资源时, using()块不起作用。 这就是Scoped服务的用武之地,错误地使用它们会导致程序中的资源泄漏。

你也可以添加

 .UseDefaultServiceProvider(options => options.ValidateScopes = false) 

Program.cs文件中.Build()之前禁用validation。

尝试仅用于开发测试,ActiveUsersService是单例,并且具有比MongoDbContext更大的生命周期,MongoDbContext是作用域并且不会被释放。

还有另一种方法可以解决这个问题,它是将MongoDbContext作为AddTransient添加到DI中,如下所示:

 services.AddSingleton(); services.AddTransient(); 

使用这种方法的意思是,对于使用它的每个Singleton类,最终会得到一个MongoDbContext实例。 例如,如果您使用MongoDbContext有10个Singleton类,那么它将有10个实例,但它不是为每个请求创建一个实例。

请参阅此参考: 无法从Singleton消耗Scoped服务 – ASP.net核心DI范围的一课