Передача данных в зависимости, зарегистрированные в жизненном цикле контекста выполнения в 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 доступны для фонового потока, но для того, чтобы справиться с этим, вам необходимо убедиться, что:

  • Реализация неизменна и, следовательно, поточно-ориентирована.
  • Реализация не требует утилизации, потому что обычно она удаляется после завершения исходного запроса.
Другие вопросы по тегам