1

Simple Injector で Execution Context Scope または Lifetime Scope のいずれかに登録された依存関係にデータを渡す方法はありますか?

私の依存関係の 1 つは、依存関係チェーンで構築するためにデータの一部を必要とします。HTTP および WCF 要求の間、このデータは簡単に取得できます。HTTP 要求の場合、データは常にクエリ文字列またはRequest.Formパラメータとして存在します (したがって、から利用できますHttpContext.Current)。WCF 要求の場合、データは常にOperationContext.Current.RequestContext.RequestMessageXML に存在し、解析できます。このデータを必要とするインターフェイスの実装に依存する多くのコマンド ハンドラーの実装があり、それらは HTTP および WCF スコープのライフスタイルでうまく機能します。

ここで、Task Parallel Library を使用してこれらのコマンドの 1 つ以上を実行できるようにして、別のスレッドで実行できるようにしたいと考えています。データの一部を構成ファイル、クラス、またはその他の静的アーティファクトに移動することは現実的ではありません。最初に、HTTP または WCF を介してアプリケーションに渡す必要があります。

TaskSimple Injector を使用してハイブリッド ライフスタイルを作成する方法を知っており、既にハイブリッド HTTP / WCF / Execution Context Scope (コマンド インターフェイスは非同期であり、代わりに戻ります) としてセットアップされていますvoid。また、必要に応じて新しい実行コンテキスト スコープを開始するコマンド ハンドラー デコレーターを作成する方法も知っています。問題は、依存関係チェーンが依存関係の1つを構築するために必要なときに利用できるように、このデータの一部を「保存」する方法または場所(またはできるかどうか)がわからないことです。

出来ますか?もしそうなら、どのように?

アップデート

現在、IProvideHostWebUri2 つの実装で呼び出されるインターフェイスがあります: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

したがって、最終的には、このアプローチを根絶しない限り、私の目標は、このインターフェイスの 3 番目の実装を作成することです。これは、ある種のコンテキストに依存して Uri を取得します (これは、他の 2 つの実装の文字列から構築されたものです)。

@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抽象化を使用していますが、他の種類のコンテキスト情報も明らかに同様に機能することに注意してください。

デコレータは、コマンドをバックグラウンドで処理できる小さなインフラストラクチャであり、必要なすべてのコンテキスト情報がバックグラウンド スレッドにも移動されるようにする役割があります。

これを可能にするために、コンテキスト情報がキャプチャされ、バックグラウンド スレッドでクロージャーとして使用されます。このための追加の抽象化、つまり を作成しましたITenantContextApplierITenantContextテナント コンテキストの実装では、と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なものをバックグラウンド スレッドで利用できるようにすることですが、これを実行できるようにするには、次のことを確認する必要があります。

  • 実装は不変であるため、スレッドセーフです。
  • 通常、元のリクエストが終了すると破棄されるため、実装では破棄する必要はありません。
于 2015-02-18T13:31:45.083 に答える