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のパイプライン

Source: https://habr.com/ru/post/J311256/


All Articles