web-dev-qa-db-ja.com

Unityのシングルトンパーコールコンテキスト(Webリクエスト)

数日前、ASP.Netのスレッド化に問題がありました。 Webリクエストごとにシングルトンオブジェクトが必要でした。私は実際に私の仕事の単位のためにこれを必要としています。 IDマップがリクエスト全体で有効になるように、Webリクエストごとに作業単位をインスタンス化したかったのです。このようにして、IoCを使用して独自のIUnitOfWorkをリポジトリクラスに透過的に挿入し、同じインスタンスを使用してエンティティをクエリして更新することができます。

Unityを使用しているため、誤ってPerThreadLifeTimeManagerを使用しました。 ASP.Netスレッドモデルが私が達成したいことをサポートしていないことにすぐに気づきました。基本的にはスレッドプールを使用してスレッドをリサイクルします。つまり、スレッドごとに1つのUnitOfWorkを取得します。しかし、私が欲しかったのは、Webリクエストごとに1つの作業単位でした。

少しグーグルで私に この素晴らしい投稿 。それはまさに私が望んでいたことでした。非常に簡単に達成できた統一部分を除いて。

これは、Unity用のPerCallContextLifeTimeManagerの実装です。

public class PerCallContextLifeTimeManager : LifetimeManager
{
    private const string Key = "SingletonPerCallContext";

    public override object GetValue()
    {
        return CallContext.GetData(Key);
    }

    public override void SetValue(object newValue)
    {
        CallContext.SetData(Key, newValue);
    }

    public override void RemoveValue()
    {
    }
}

そしてもちろん、これを使用して、作業単位を次のようなコードで登録します。

unityContainer
            .RegisterType<IUnitOfWork, MyDataContext>(
            new PerCallContextLifeTimeManager(),
            new InjectionConstructor());

それが誰かの時間を少し節約することを願っています。

45
Mehdi Khalili

きちんとした解決策ですが、LifetimeManagerの各インスタンスは、定数ではなく一意のキーを使用する必要があります。

private string _key = string.Format("PerCallContextLifeTimeManager_{0}", Guid.NewGuid());

そうしないと、PerCallContextLifeTimeManagerに複数のオブジェクトが登録されている場合、それらはCallContextにアクセスするための同じキーを共有しているため、期待されるオブジェクトを取り戻すことはできません。

オブジェクトが確実にクリーンアップされるように、RemoveValueを実装する価値もあります。

public override void RemoveValue()
{
     CallContext.FreeNamedDataSlot(_key);
}
25
sixeyed

これをPerCallContextLifeTimeManagerと呼んでも問題ありませんが、これはASP.Netの要求ごとのLifeTimeManagerと見なされるのにnot「安全」ではないと確信しています。

ASP.Netがスレッドスワップを実行する場合、CallContextを介して新しいスレッドに渡されるのみは、現在のHttpContextです-CallContextに格納するその他のもの消えてしまいます。これは、高負荷の下で上記のコードが意図しない結果をもたらす可能性があることを意味します-そして、その理由を突き止めるのは本当に苦痛だと思います!

これを行うための唯一の「安全な」方法は、HttpContext.Current.Itemsを使用するか、次のようなことを行うことです。

public class PerCallContextOrRequestLifeTimeManager : LifetimeManager
{
    private string _key = string.Format("PerCallContextOrRequestLifeTimeManager_{0}", Guid.NewGuid());

    public override object GetValue()
    {
      if(HttpContext.Current != null)
        return GetFromHttpContext();
      else
        return GetFromCallContext();
    }

    public override void SetValue(object newValue)
    {
      if(HttpContext.Current != null)
        return SetInHttpContext();
      else
        return SetInCallContext();
    }

    public override void RemoveValue()
    {
    }
}

これは明らかにSystem.Webへの依存関係を取ることを意味します:

これに関する詳細は、次のURLで入手できます。

---(http://piers7.blogspot.com/2005/11/threadstatic-callcontext-and_02.html

21
Steven Robbins

あなたの貢献に感謝します、

しかし、提案された実装には2つの欠点があり、そのうちの1つは、Steven Robbinsが 彼の回答 およびMicah Zoltu コメント ですでに述べた深刻なバグです。

  1. 呼び出しコンテキストは、単一の要求に対してAsp.Netによって保持されることが保証されていません。負荷がかかると、別の実装に切り替わり、提案された実装が中断する可能性があります。
  2. リクエスト終了時の依存関係の解放は処理しません。

現在、Unity.Mvc Nugetパッケージは、作業を行うための PerRequestLifetimeManager を提供しています。関連するUnityPerRequestHttpModuleをブートストラップコードに登録することを忘れないでください。そうしないと、依存関係の解放も処理されません。

ブートストラップの使用

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

またはweb.configsystem.webServer/modules

<add name="UnityPerRequestHttpModule" type="Microsoft.Practices.Unity.Mvc.UnityPerRequestHttpModule, Microsoft.Practices.Unity.Mvc" preCondition="managedHandler" />

現在の実装はWebフォームにも適しているようです。そしてそれはMVCにも依存しません。残念ながら、アセンブリには他のクラスが含まれているため、アセンブリはそうします。

解決された依存関係を使用してカスタムhttpモジュールを使用する場合、それらはモジュールEndRequestにすでに配置されている可能性があることに注意してください。モジュールの実行順序によって異なります。

4
Frédéric