Внедрение зависимостей Microsoft. Как я могу получить данные в класс, внедренный конструктором, на несколько уровней глубже

#c# #dependency-injection #.net-core

#c# #внедрение зависимостей #.net-core

Вопрос:

Это проект dotNet core 2.2, использующий Microsoft.Расширения.DependencyInjection.

У меня есть 3 класса. Класс A использует класс B в конструкторе. Класс B использует класс C, а класс C использует интерфейс ITenant.

ITenant определяет, какая база данных будет использоваться.

пример:

 public A(IB b)
public B(IC c)
public C(ITenant t)
  

Они настраиваются в контейнере для внедрения следующим образом:

 services.AddTransient<IA, A>();
services.AddTransient<IB, b>();
services.AddTransient<IC, c>();
services.AddTransient<ITenant , HttpTenant>()>();
  

В веб-проекте контроллер использует класс A в качестве параметра конструктора и контейнер createClass A и все его зависимости. Реализация ITenant (HttpTenant) извлекает имя клиента из заголовка HTTP-запроса и получает информацию о базе данных из файла конфигурации. Все работает отлично.

Теперь мне нужно вызвать это из службы Windows, которая не использует HTTP-запрос as. У меня есть обработчик, который отвечает на очередь сообщений, а класс A является параметром построения. Для службы Windows у меня другой ITenant (WindowServiceTenant):

 services.AddTransient<ITenant , WindowServiceTenant>()>();
  

Я не могу понять, как получить код клиента в WindowServiceTenant.

  • Клиент определяется во время выполнения на основе значения, считанного из очереди сообщений.
  • К моменту создания экземпляра моего обработчика также создается экземпляр WindowServiceTenant.
  • Я не знаю клиента до установки обработчика.

Мне нужно получить ссылку на этот экземпляр WindowServiceTenant и предоставить клиента. Или для этой реализации WindowServiceTenant требуется ссылка на обработчик, который инициировал создание экземпляра.

Есть идеи?

Ответ №1:

В основном есть два решения:

  1. Настройте WindowServiceTenant экземпляр с требуемым значением, прежде чем разрешать обработчик
  2. Передайте значение через окружающее состояние, например, значение, доступное для потока ( ThreadLocal<T> ) или асинхронной операции ( AsyncLocal<T> )

Первый вариант требует, чтобы WindowServiceTenant был зарегистрирован как Scoped сервис и создания IServiceScope , из которого вы разрешаете WindowServiceTenant , и соответствующего обработчика:

 // Registration
services.AddScoped<WindowServiceTenant>();
services.AddScoped<ITenant>(c => c.GetRequiredService<WindowServiceTenant>());

// Usage
using (var scope = serviceProvider.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var tenant = services.GetRequiredService<WindowServiceTenant>();

    // Set the right tenant based on a value from the queue
    tenant.SetTenantValue(...);

    // Resolve and execute handler
    var handler = services.GetRequiredService(handlerType);
}
  

Предыдущий список кода выполняет следующее:

  • Он регистрирует WindowServiceTenant как по своему конкретному типу, так и по своему интерфейсу таким образом, что разрешение WindowServiceTenant и ITenant приведет к одному и тому же экземпляру в рамках одной области обслуживания. Это важно, потому что в противном случае состояние устанавливается для этого экземпляра с ограниченной областью. Наличие нескольких экземпляров в одной и той же области обслуживания, очевидно, не даст правильного результата.
  • Когда ваше сообщение обработано, вы запускаете новое, IServiceScope используя CreateScope метод расширения на IServiceProvider .
  • В пределах этой области, которую вы разрешаете WindowServiceTenant . Вы разрешаете этот конкретный тип, поскольку ITenant абстракция не будет иметь возможности установить правильное значение (поскольку это деталь реализации)
  • Вы сохраняете значение клиента из очереди внутри WindowServiceTenant экземпляра. Поскольку этот же экземпляр повторно используется в течение области обслуживания, он будет внедрен в любой разрешенный граф объектов, который зависит от ITenant .