Использование сервисов scoped внутри singletons

  • Михаил
  • 8 мин. на прочтение
  • 68
  • 05 Sep 2023
  • 05 Sep 2023

Хотелось бы сразу предупредить, что попытка использовать сервисы с ограниченной областью действия внутри singletons (или использование любого недолговечного сервиса внутри долгоживущего сервиса, если уж на то пошло!), как правило, является плохой идеей. Есть причина, по которой большинство контейнеров внедрения зависимостей (DI) пытаются помешать вам это сделать. Попытка использовать сервисы с ограниченной областью действия внутри singletons может привести к так называемым Captive Dependency , которые могут вызвать всевозможные неприятные ошибки и утечки памяти. Если вы окажетесь в ситуации, когда вы пытаетесь внедрить сервис с ограниченной областью действия в singletons, это, как правило, code smell, и вам следует серьезно подумать о рефакторинге своих сервисов, чтобы избежать этой зависимости. Тем не менее, иногда существуют законные причины для использования сервисов с ограниченной областью действия внутри singletons. Если вы считаете, что это верно для вашего варианта использования, читайте дальше.

Вы когда-нибудь пытались внедрить одну службу в другую, но получали следующее исключение?:

InvalidOperationException: Cannot resolve scoped service 'Report' from root provider.

Введение
Если да, то это хороший признак того, что вы, возможно, пытаетесь внедрить службу с ограниченной областью в одноэлементную службу. Исключение появляется потому, что контейнер DI пытается защитить вас от зависимостей (см. заявление об отказе от ответственности выше). Хотя в целом хорошо, что DI-контейнер пытается помешать вам делать такие вещи, иногда это необходимо.

Недавно я столкнулся с этим при попытке создать размещенную службу в ASP.NET Core . Размещенная служба позволяет создавать длительные фоновые задачи и, по сути, ведет себя как одноэлементная служба ( с некоторыми небольшими оговорками).

Некоторые из моих фоновых задач требуют использования других служб, большинство из которых являются временными или ограниченными. Однако когда я попытался внедрить эти сервисы в конструктор моего размещенного сервиса, я получил исключение, подобное приведенному выше.

Проблема

Контейнер DI ASP.NET Core имеет корень IServiceProvider, который используется для разрешения одноэлементных служб. Для служб с ограниченной областью контейнер должен сначала создать новую область, и каждая область будет иметь свою собственную область IServiceProvider. Доступ к сервисам с ограниченной областью действия возможен только из IServiceProviderих собственной области, а не из корня IServiceProvider.

Решение

Чтобы иметь возможность использовать службы с ограниченной областью внутри singletons, необходимо создать область вручную. Новую область действия можно создать, внедрив IServiceScopeFactoryв ваш singletons-сервис (он IServiceScopeFactoryсам по себе является singletons, поэтому это работает). Имеет IServiceScopeFactoryметод CreateScope, который используется для создания новых экземпляров области.

public class MySingletonService
{
  private readonly IServiceScopeFactory _serviceScopeFactory;

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

  public void Execute()
  {
    using (var scope = _serviceScopeFactory.CreateScope())
    {
      var report = scope.ServiceProvider.GetService<Report>();

      report.InfoToTelegram("message ......");
    }
  }
}

Созданная область имеет собственную область IServiceProvider, к которой вы можете получить доступ для разрешения своих служб с заданной областью.

Важно убедиться, что область действия существует только столько, сколько необходимо, и что она правильно утилизируется после завершения работы с ней. Это сделано для того, чтобы избежать проблем с пленными зависимостями (как обсуждалось в начале этой статьи). Поэтому я бы рекомендовал:

  • Определите область действия только внутри метода, который вы собираетесь использовать. Может возникнуть соблазн назначить его полю для повторного использования в другом месте одноэлементной службы, но это опять-таки приведет к зависимым зависимостям.
  • Оберните область видимости в usingоператор. Это обеспечит правильную утилизацию прицела после того, как вы закончите с ним работать.

Заключение

Хотя попытка разрешить ограниченные службы в рамках singletons часто может быть признаком того, что ваш код нуждается в рефакторинге, иногда это все же необходимо сделать.

Службы с ограниченной областью действия можно использовать в одиночных экземплярах, создав область и используя IServiceProviderобласть действия. Однако важно убедиться, что область очищается после ее использования, чтобы предотвратить связанные зависимости.