Передача данных в зависимости, зарегистрированные в жизненном цикле контекста выполнения в Simple Injector
Есть ли способ передать данные зависимостям, зарегистрированным в Scope Context Scope или Lifetime Scope в Simple Injector?
Одна из моих зависимостей требует фрагмента данных для построения в цепочке зависимостей. Во время запросов HTTP и WCF эти данные легко получить. Для HTTP-запросов данные всегда присутствуют либо в строке запроса, либо в виде Request.Form
параметр (и, следовательно, доступен из HttpContext.Current
). Для запросов WCF данные всегда присутствуют в OperationContext.Current.RequestContext.RequestMessage
XML, и может быть проанализирован. У меня есть много реализаций обработчиков команд, которые зависят от реализации интерфейса, которая нуждается в этом фрагменте данных, и они прекрасно работают во время жизни в рамках HTTP и WCF.
Теперь я хотел бы иметь возможность выполнять одну или несколько из этих команд с помощью библиотеки параллельных задач, чтобы она выполнялась в отдельном потоке. Невозможно переместить часть данных в файл конфигурации, класс или любой другой статический артефакт. Первоначально он должен быть передан приложению через HTTP или WCF.
Я знаю, как создать гибридный образ жизни с помощью Simple Injector, и я уже настроил его как гибридный HTTP / WCF / контекст контекста выполнения (командные интерфейсы асинхронны и возвращают Task
вместо void
). Я также знаю, как создать декоратор обработчика команд, который при необходимости запускает новую область контекста выполнения. Проблема в том, что я не знаю, как и где (или если смогу) "сохранить" этот фрагмент данных, чтобы он был доступен, когда цепочка зависимостей нуждается в нем для построения одной из зависимостей.
Является ли это возможным? Если так, то как?
Обновить
В настоящее время у меня есть интерфейс под названием IProvideHostWebUri
с двумя реализациями: HttpHostWebUriProvider
а также WcfHostWebUriProvider
, Интерфейс и регистрация выглядят так:
public interface IProvideHostWebUri
{
Uri HostWebUri { get; }
}
container.Register<IProvideHostWebUri>(() =>
{
if (HttpContext.Current != null)
return container.GetInstance<HttpHostWebUriProvider>();
if (OperationContext.Current != null)
return container.GetInstance<WcfHostWebUriProvider>();
throw new NotSupportedException(
"The IProvideHostWebUri service is currently only supported for HTTP and WCF requests.");
}, scopedLifestyle); // scopedLifestyle is the hybrid mentioned previously
Таким образом, в конечном итоге, если я не потрошу этот подход, моей целью будет создание третьей реализации этого интерфейса, которая затем будет зависеть от некоторого контекста для получения Uri (который просто создается из строки в двух других реализациях).
@ Стивен, похоже, ответ, что я ищу, но я не уверен, как сделать ITenantContext
реализация неизменна и потокобезопасна. Я не думаю, что это нужно сделать одноразовым, так как он просто содержит Uri
значение.
1 ответ
Так что вы в основном говорите, что:
- У вас есть первоначальный запрос, который содержит некоторую контекстную информацию, захваченную в заголовке запроса.
- Во время этого запроса вы хотите запустить фоновую операцию (в другом потоке).
- Контекстная информация из первоначального запроса должна оставаться доступной при запуске в фоновом потоке.
Короткий ответ: Simple Injector не содержит ничего, что позволяет вам это делать. Решение заключается в создании части инфраструктуры, которая позволяет перемещать эту контекстную информацию.
Например, вы обрабатываете обработчики команд (дикая догадка;-)), вы можете указать декоратор следующим образом:
public class BackgroundProcessingCommandHandlerDecorator<T> : ICommandHandler<T>
{
private readonly ITenantContext tenantContext;
private readonly Container container;
private readonly Func<ICommandHandler<T>> decorateeFactory;
public BackgroundProcessingCommandHandlerDecorator(ITenantContext tenantContext,
Container container, Func<ICommandHandler<T>> decorateeFactory) {
this.tenantContext = tenantContext;
this.container = container;
this.decorateeFactory = decorateeFactory;
}
public void Handle(T command) {
// Capture the contextual info in a local variable
// NOTE: This object must be immutable and thread-safe.
var tenant = this.tenantContext.CurrentTenant;
// Kick off a new background operation
Task.Factory.StartNew(() => {
using (container.BeginExecutionContextScope()) {
// Load a service that allows setting contextual information
var context = this.container.GetInstance<ITenantContextApplier>();
// Set the context for this thread, before resolving the handler
context.SetCurrentTenant(tenant);
// Resolve the handler
var decoratee = this.decorateeFactory.Invoke();
// And execute it.
decoratee.Handle(command);
}
});
}
}
Обратите внимание, что в примере я использую воображаемый ITenantContext
абстракция, предполагая, что вам нужно предоставить командам информацию о текущем арендаторе, но любой другой вид контекстной информации, очевидно, тоже подойдет.
Декоратор - это небольшая часть инфраструктуры, которая позволяет обрабатывать команды в фоновом режиме, и он несет ответственность за то, чтобы вся необходимая контекстная информация также перемещалась в фоновый поток.
Чтобы сделать это, контекстная информация собирается и используется в качестве замыкания в фоновом потоке. Я создал для этого дополнительную абстракцию, а именно ITenantContextApplier
, Обратите внимание, что реализация контекста арендатора может реализовать как ITenantContext
и ITenantContextApplier
интерфейс. Однако, если вы определяете ITenantContextApplier
в корне композиции приложение не сможет изменить контекст, так как оно не зависит от ITenantContextApplier
,
Вот пример:
// Base library
public interface ITenantContext { }
// Business Layer
public class SomeCommandHandler : ICommandHandler<Some> {
public SomeCommandHandler(ITenantContext context) { ... }
}
// Composition Root
public static class CompositionRoot {
// Make the ITenantContextApplier private so nobody can see it.
// Do note that this is optional; there's no harm in making it public.
private interface ITenantContextApplier {
void SetCurrentTenant(Tenant tenant);
}
private class AspNetTenantContext : ITenantContextApplier, ITenantContext {
// Implement both interfaces
}
private class BackgroundProcessingCommandHandlerDecorator<T> { ... }
public static Container Bootstrap(Container container) {
container.RegisterPerWebRequest<ITenantContext, AspNetTenantContext>();
container.Register<ITenantContextApplier>(() =>
container.GetInstance<ITenantContext>() as ITenantContextApplier);
container.RegisterDecorator(typeof(ICommandHandler<>),
typeof(BackgroundProcessingCommandHandlerDecorator<>));
}
}
Другой подход будет просто сделать полный ITenantContext
доступны для фонового потока, но для того, чтобы справиться с этим, вам необходимо убедиться, что:
- Реализация неизменна и, следовательно, поточно-ориентирована.
- Реализация не требует утилизации, потому что обычно она удаляется после завершения исходного запроса.