1

有没有办法将数据传递给在 Simple Injector 中使用 Execution Context Scope 或 Lifetime Scope 注册的依赖项?

我的一个依赖项需要一段数据才能在依赖项链中构建。在 HTTP 和 WCF 请求期间,这些数据很容易获得。对于 HTTP 请求,数据始终存在于查询字符串中或作为Request.Form参数(因此可从 获取HttpContext.Current)。对于 WCF 请求,数据始终存在于OperationContext.Current.RequestContext.RequestMessageXML 中,并且可以被解析出来。我有许多命令处理程序实现,它们依赖于需要这条数据的接口实现,它们在 HTTP 和 WCF 范围的生活方式中运行良好。

现在我希望能够使用任务并行库执行这些命令中的一个或多个,以便它将在单独的线程中执行。将数据块移出到配置文件、类或任何其他静态工件中是不可行的。它最初必须通过 HTTP 或 WCF 传递给应用程序。

我知道如何使用 Simple Injector 创建一种混合生活方式,并且已经将其设置为混合 HTTP / WCF / 执行上下文范围(命令接口是异步的,并且返回Task而不是返回void)。我也知道如何创建一个命令处理程序装饰器,它将在需要时启动一个新的执行上下文范围。问题是,我不知道如何或在哪里(或者如果我可以)“保存”这段数据,以便在依赖链需要它来构建依赖项之一时可用。

可能吗?如果是这样,怎么做?

更新

目前,我有一个IProvideHostWebUri使用两个实现调用的接口:HttpHostWebUriProviderWcfHostWebUriProvider. 界面和注册如下所示:

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(它只是从其他两个实现中的字符串构造的)。

@Steven 的答案似乎是我正在寻找的,但我不确定如何使ITenantContext实现不可变和线程安全。我认为它不需要一次性使用,因为它只包含一个Uri值。

4

1 回答 1

2

所以你基本上是在说:

  • 您有一个初始请求,其中包含在请求“标头”中捕获的一些上下文信息。
  • 在此请求期间,您想要启动后台操作(在不同的线程上)。
  • 在后台线程中运行时,来自初始请求的上下文信息应保持可用。

简短的回答是 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. 请注意,租户上下文实现可以同时实现接口ITenantContextITenantContextApplier接口。但是,如果您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后台线程完全可用,但为了能够实现这一点,您需要确保:

  • 该实现是不可变的,因此是线程安全的。
  • 该实现不需要处置,因为它通常会在原始请求结束时被处置。
于 2015-02-18T13:31:45.083 回答