#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:
В основном есть два решения:
- Настройте
WindowServiceTenant
экземпляр с требуемым значением, прежде чем разрешать обработчик - Передайте значение через окружающее состояние, например, значение, доступное для потока (
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
.