Web APIとOWINミドルウェア間のHTTP要求の一部として単一のIoCコンテナーを使用する

この記事の目的は、リクエストの存続期間を通じて依存関係の単一のコンテナー(IoCコンテナー)を作成し、その作成と破棄を制御できる実用的なソリューションを見つけることです。



画像



これは、Webアプリケーションがトランザクション対応である必要がある場合(そして、私の意見では、Webアプリケーションがそれを保持する必要がある場合、つまり、要求が正常に処理された場合にのみ変更を(たとえば、データベースに)適用し、いずれかの段階でエラーが発生し、誤った結果と制御不能な結果を​​示しています )( githubソースコード )。



理論



Web API 2プロジェクトは、着信リクエストパイプラインの構築を支援するように設計されたIAppBuilder OWINインターフェイスを使用して構成されます。



上記の画像は、リクエストのライフサイクルを示しています-チェーンのすべてのコンポーネントを通過し、Web API(別のコンポーネントでもあります)に戻り、サーバーからの応答を形成、装飾します。



単一の依存関係コンテナーを使用するには、要求処理の開始時に明示的に作成し、完了時に破棄する必要があります。



  1. リクエスト処理の開始。
  2. コンテナ作成;
  3. ミドルウェアでのコンテナの使用。
  4. Web APIでコンテナーを使用します。
  5. コンテナの破壊。
  6. 要求処理の完了。


これを行うには、コンテナを構成し、Web APIに登録するだけで十分です(DependencyResolverを使用)。



// Configure our parent container var container = UnityConfig.GetConfiguredContainer(); // Pass our parent container to HttpConfiguration (Web API) var config = new HttpConfiguration { DependencyResolver = new UnityDependencyResolver(container) }; WebApiConfig.Register(config);
      
      





子コンテナを作成する独自のミドルウェアを作成します。



 public class UnityContainerPerRequestMiddleware : OwinMiddleware { public UnityContainerPerRequestMiddleware(OwinMiddleware next, IUnityContainer container) : base(next) { _next = next; _container = container; } public override async Task Invoke(IOwinContext context) { // Create child container (whose parent is global container) var childContainer = _container.CreateChildContainer(); // Set created container to owinContext // (to become available at other places using OwinContext.Get<IUnityContainer>(key)) context.Set(HttpApplicationKey.OwinPerRequestUnityContainerKey, childContainer); await _next.Invoke(context); // Dispose container that would dispose each of container's registered service childContainer.Dispose(); } private readonly OwinMiddleware _next; private readonly IUnityContainer _container; }
      
      





それを他のミドルウェアで使用します(私の実装では、context.Setを使用してグローバルOwinContextにコンテナを保存します。context.Setは次の各ミドルウェアに渡され、context.Getを使用して取得されます)。



 public class CustomMiddleware : OwinMiddleware { public CustomMiddleware(OwinMiddleware next) : base(next) { _next = next; } public override async Task Invoke(IOwinContext context) { // Get container that we set to OwinContext using common key var container = context.Get<IUnityContainer>( HttpApplicationKey.OwinPerRequestUnityContainerKey); // Resolve registered services var sameInARequest = container.Resolve<SameInARequest>(); await _next.Invoke(context); } private readonly OwinMiddleware _next; }
      
      





1つのBUTでない場合、これを完了することができます。



問題



ミドルウェアWeb APIには、次のような独自のリクエスト処理サイクルが内部的にあります。



  1. 要求はHttpServerに送られ、HttpRequestMessageの処理を開始してルーティングシステムに転送します。
  2. HttpRoutingDispatcherは、リクエストからデータを取得し、ルートテーブルを使用して処理を行うコントローラーを決定します。
  3. HttpControllerDispatcherで、以前に定義されたコントローラーが作成され、要求処理メソッドが呼び出されてHttpResponseMessageが形成されます。


DefaultHttpControllerActivatorの次の行は、コントローラーの作成を担当します。



 IHttpController instance = (IHttpController)request.GetDependencyScope().GetService(controllerType);
      
      





GetDependencyScopeメソッドのメインコンテンツ



 public static IDependencyScope GetDependencyScope(this HttpRequestMessage request) { // … IDependencyResolver dependencyResolver = request.GetConfiguration().DependencyResolver; result = dependencyResolver.BeginScope(); request.Properties[HttpPropertyKeys.DependencyScope] = result; request.RegisterForDispose(result); return result; }
      
      





Web APIがDependencyResolverを要求し、HttpConfigurationで登録し、dependencyResolver.BeginScope()を使用して子コンテナーを作成し、その中に要求の処理を担当するコントローラーのインスタンスが作成されることがわかります。



私たちにとって、これは次を意味します:ミドルウェアとWeb APIで使用するコンテナは同じではありません-さらに、それらは同じレベルのネストにあり、グローバルコンテナはそれらの共通の親です。 :



  1. グローバルコンテナ。

    1. UnityContainerPerRequestMiddlewareで作成された子コンテナー。
    2. Web APIで作成された子コンテナー。


Web APIの場合、これがリクエストを処理する唯一の場所である場合、これは非常に論理的に見えます。コンテナは最初に作成され、最後に破棄されます(これはまさに私たちが達成しようとしていることです)。



ただし、現時点では、Web APIはパイプライン内のリンクの1つにすぎません。つまり、独自のコンテナーの作成を放棄する必要があります-私たちのタスクは、この動作を再定義し、コントローラーを作成して依存関係を解決するためにWeb APIが必要なコンテナーを指定することです



解決策



上記の問題を解決するために、独自のIHttpControllerActivatorを実装することができます。このメソッドの作成メソッドには、以前に作成され、既にその中にある依存関係を解決するコンテナを受け取ります。



 public class ControllerActivator : IHttpControllerActivator { public IHttpController Create( HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType ) { // Get container that we set to OwinContext using common key var container = request.GetOwinContext().Get<IUnityContainer>( HttpApplicationKey.OwinPerRequestUnityContainerKey); // Resolve requested IHttpController using current container // prevent DefaultControllerActivator's behaviour of creating child containers var controller = (IHttpController)container.Resolve(controllerType); // Dispose container that would dispose each of container's registered service // Two ways of disposing container: // 1. At UnityContainerPerRequestMiddleware, after owin pipeline finished (WebAPI is just a part of pipeline) // 2. Here, after web api pipeline finished (if you do not use container at other middlewares) (uncomment next line) // request.RegisterForDispose(new Release(() => container.Dispose())); return controller; } }
      
      





Web APIで使用するために残っているのは、構成内の標準のHttpControllerActivatorを置き換えることだけです。



 var config = new HttpConfiguration { DependencyResolver = new UnityDependencyResolver(container) }; // Use our own IHttpControllerActivator implementation // (to prevent DefaultControllerActivator's behaviour of creating child containers per request) config.Services.Replace(typeof(IHttpControllerActivator), new ControllerActivator()); WebApiConfig.Register(config);
      
      





したがって、単一のコンテナを操作するための次のメカニズムを取得します。



1.リクエストの処理の開始。



2.グローバルから子コンテナを作成します。



 var childContainer = _container.CreateChildContainer();
      
      





3.コンテナをOwinContextに割り当てます。



 context.Set(HttpApplicationKey.OwinPerRequestUnityContainerKey, childContainer);
      
      





4.他のミドルウェアでのコンテナの使用。



 var container = context.Get<IUnityContainer>(HttpApplicationKey.OwinPerRequestUnityContainerKey);
      
      





5. Web APIでコンテナーを使用します。



5.1。 OwinContextからコントローラーを取得する:



 var container = request.GetOwinContext().Get<IUnityContainer>(HttpApplicationKey.OwinPerRequestUnityContainerKey);
      
      





5.2。 このコンテナーに基づいてコントローラーを作成します。



 var controller = (IHttpController)container.Resolve(controllerType);
      
      





6.コンテナの破壊:



 childContainer.Dispose();
      
      





7.要求処理の完了。



結果



必要なライフサイクルに従って依存関係を構成します。



 public static void RegisterTypes(IUnityContainer container) { // ContainerControlledLifetimeManager - singleton's lifetime container.RegisterType<IAlwaysTheSame, AlwaysTheSame>(new ContainerControlledLifetimeManager()); container.RegisterType<IAlwaysTheSame, AlwaysTheSame>(new ContainerControlledLifetimeManager()); // HierarchicalLifetimeManager - container's lifetime container.RegisterType<ISameInARequest, SameInARequest>(new HierarchicalLifetimeManager()); // TransientLifetimeManager (RegisterType's default) - no lifetime container.RegisterType<IAlwaysDifferent, AlwaysDifferent>(new TransientLifetimeManager()); }
      
      





  1. ContainerControlledLifetimeManager-アプリケーション内に単一のインスタンスを作成します。
  2. HierarchicalLifetimeManager-コンテナー内に単一のインスタンスを作成します(コンテナーがHTTPリクエスト内で単一であることを確認しました)。
  3. TransientLifetimeManager-呼び出しごとにインスタンスを作成します(解決)。


画像






上記の画像は、いくつかのHTTPリクエストのコンテキストでGetHashCodeの依存関係を示しています。



  1. AlwaysTheSame-アプリケーション内のシングルトンオブジェクト。
  2. SameInARequest-リクエスト内のシングルトンオブジェクト。
  3. AlwaysDifferent-各Resolveの新しいインスタンス。


»ソースはgithubで入手できます。



材料:

1. ASP.NET Web APIのパイプライン



All Articles