Можно ли завершить жизненный цикл Scoped-сервиса раньше завершения HTTP-запроса в ASP.NET Core?

«Можно ли завершить жизненный цикл Scoped-сервиса раньше завершения HTTP-запроса в ASP.NET Core?» — вопрос из категории ASP.NET Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, можно. Основной IServiceScope, связанный с HTTP-запросом, завершается автоматически в конце конвейера Middleware. Однако вы можете вручную создать и завершить дополнительный scope для изолированной операции, не дожидаясь конца запроса.

Практический пример:

// Внутри метода контроллера или сервиса
public void PerformIsolatedOperation(IServiceProvider rootProvider)
{
    // Создаем новый, независимый scope
    using (var isolatedScope = rootProvider.CreateScope())
    {
        // Получаем сервисы из этого scope
        var scopedService = isolatedScope.ServiceProvider
            .GetRequiredService<IMyScopedService>();
        var dbContext = isolatedScope.ServiceProvider
            .GetRequiredService<ApplicationDbContext>();

        // Выполняем операцию...
        scopedService.Process(dbContext);
    } // Здесь isolatedScope будет уничтожен, а вместе с ним
      // и все созданные в нем Scoped-сервисы (DbContext и др.)
}

Ключевые моменты и best practices:

  • Изоляция: Созданный вручную scope полностью независим от scope текущего запроса. Это полезно для фоновых операций в рамках запроса или для работы с данными в отдельной транзакции.
  • Освобождение ресурсов: Используйте конструкцию using для гарантированного вызова Dispose() у scope, что корректно освободит все ресурсы (например, соединение с БД в DbContext).
  • Service Locator: Получение сервисов через ServiceProvider внутри метода считается антипаттерном Service Locator. Лучше внедрять IServiceScopeFactory.

Улучшенный подход с внедрением IServiceScopeFactory:

public class MyService
{
    private readonly IServiceScopeFactory _scopeFactory;
    public MyService(IServiceScopeFactory scopeFactory) => _scopeFactory = scopeFactory;

    public void IsolatedWork()
    {
        using var scope = _scopeFactory.CreateScope();
        var dependency = scope.ServiceProvider.GetService<IDependency>();
        // ... работа
    }
}