Что такое Scoped-сервис в ASP.NET Core DI?

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

Ответ

Scoped — это одно из трёх основных времён жизни сервиса в системе внедрения зависимостей (DI) ASP.NET Core. Scoped-сервис создаётся один раз в рамках определённой области (scope) и используется повторно всеми компонентами внутри этой области.

Ключевое применение: В веб-приложении каждый HTTP-запрос автоматически создаёт новую область. Поэтому Scoped-сервис живёт ровно столько, сколько длится обработка одного запроса.

Регистрация:

// В Program.cs или Startup.ConfigureServices
services.AddScoped<IMyService, MyService>();
services.AddScoped<ApplicationDbContext>(); // Типичный пример для EF Core

Почему это важно? Практический пример с Entity Framework Core: Контекст БД (DbContext) по умолчанию регистрируется как Scoped. Это гарантирует, что:

  1. В рамках одного запроса все репозитории и сервисы работают с одним экземпляром DbContext.
  2. Отслеживание изменений (change tracking) работает корректно.
  3. В конце запроса контекст автоматически освобождается.
  4. Разные запросы не мешают друг другу, так как используют разные экземпляры.

Важные правила и ограничения:

  • Нельзя внедрять Scoped-сервис в Singleton-сервис. Это приведёт к тому, что Scoped-сервис будет захвачен Singleton'ом и станет вечно живущим, что вызовет проблемы (например, утечку памяти в DbContext).

    // НЕПРАВИЛЬНО:
    services.AddSingleton<IBadService>(provider => 
        new BadService(provider.GetRequiredService<IMyScopedService>())); // Scoped захвачен Singleton'ом
  • Для явного создания области (например, в фоновой задаче или консольном приложении) используйте IServiceScopeFactory.

    public class BackgroundWorker
    {
        private readonly IServiceScopeFactory _scopeFactory;
        public BackgroundWorker(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; }
    
        public void DoWork()
        {
            using (var scope = _scopeFactory.CreateScope())
            {
                var scopedService = scope.ServiceProvider.GetRequiredService<IMyScopedService>();
                // Работаем с scopedService в изолированной области
            } // Здесь scopedService будет уничтожен
        }
    }

Другие времена жизни для сравнения:

  • Transient: Создаётся новый экземпляр при каждом запросе сервиса. Подходит для лёгких, stateless-сервисов.
  • Singleton: Создаётся один экземпляр на всё время работы приложения. Подходит для stateless-сервисов, кэшей, конфигураций.