ASP.NET CoreアプリケーションにおけるサードパーティIoC/DIフレームワークとの統合

6705 ワード

一、ConfigureServicesメソッドが返すServiceProviderは役に立たない!
この問題を簡単な例で説明することができる.まず、実際には別のサービスプロバイダのパッケージであるMyServiceProviderを定義します.簡単に言えば、Registeメソッドを呼び出すことによって登録できるサービスインタフェースと実装タイプのマッピング関係を保存するために辞書を使用します.サービスインスタンスを提供するGetServiceメソッドでは、サービスタイプが登録されている場合、対応するインスタンスオブジェクトを作成して返します.そうしないと、パッケージされたこのサービスプロバイダを使用してサービスを提供します.サービスインスタンスが正常に回収されることを保証するために、サービスタイプがIDisposableインタフェースを実装した場合、フィールドを介して追加します.disposablesが表す集合にあります.MyServiceProviderのDisposeメソッドが呼び出されると、提供されるこれらのサービスインスタンスのDisposeメソッドが呼び出されます.

 public class MyServiceProvider : IServiceProvider, IDisposable
 {
 private IServiceProvider _innerServiceProvider;
 private Dictionary _services;
 private List _disposables;
 
 public MyServiceProvider(IServiceProvider innerServiceProvider)
 {
 _innerServiceProvider = innerServiceProvider;
 this._services = new Dictionary();
 _disposables = new List();
 }
 
 
 public MyServiceProvider Register() where TTo: TFrom, new()
 {
 _services[typeof(TFrom)] = typeof(TTo);
 return this;
 }
 
 public object GetService(Type serviceType)
 {
 Type implementation;
 if (_services.TryGetValue(serviceType, out implementation))
 {
 object service = Activator.CreateInstance(implementation);
 IDisposable disposbale = service as IDisposable;
 if (null != disposbale)
 {
  _disposables.Add(disposbale);
 }
 return service;
 }
 return _innerServiceProvider.GetService(serviceType);
 }
 
 public void Dispose()
 {
 (_innerServiceProvider as IDisposable)?.Dispose();
 foreach (var it in _disposables)
 {
 it.Dispose();
 }
 _disposables.Clear();
 }
 }

私たちは次のようにASP.でNET CoreアプリケーションではMyServiceProviderを使用します.次のコード・セグメントのように、登録されているStarupタイプでは、ConfigureServicesメソッドにMyServiceProviderオブジェクトを返します.このMyServiceProviderオブジェクトには、サービスインタフェースIoobarとインプリメンテーションタイプFoobarとのマッピングが登録されています.要求を処理する際、現在のHttpContextオブジェクトのRequestServicesプロパティを使用して、要求処理にサービスを提供するServiceProviderを取得し、登録されたIsobarサービスを取得しようとします.

 public class Program
 {
 public static void Main(string[] args)
 {
 new WebHostBuilder()
 .UseKestrel()
 .UseStartup()
 .Build()
 .Run();
 }
 }
 
 public class Startup
 {
 public IServiceProvider ConfigureServices(IServiceCollection services)
 {
 return new MyServiceProvider(services.BuildServiceProvider())
 .Register();
 }
 
 public void Configure(IApplicationBuilder app)
 {
 app.UseDeveloperExceptionPage()
 .Run(async context => await context.Response.WriteAsync(context.RequestServices.GetRequiredService().GetType().Name));
 }
 }
 public interface IFoobar { }
 public class Foobar : IFoobar { }

アプリケーション全体がこのように簡単で、問題もないようですが、アプリケーションを起動してブラウザでアクセスすると、次のようなエラーが発生します.エラーメッセージは、サービスインタフェースIoobarが登録されていないことを示します.
二、原因は何ですか.
我々は、返されたServiceProviderにIFOobarとFoobarのマッピング関係が登録されているのに、なぜRequestServicesが返されたServiceProviderは、このサービスが登録されていないと言っているのでしょうか.唯一の解釈は、ConfigureServicesメソッドが返すServiceProviderと、HttpContextのRequestServicesが返すServiceProviderがまったく同じではないことです.実際には同じオブジェクトではありません
ConfigureServicesメソッドによって返されるServiceProviderは、WebHostのServiceProviderとして機能します.WebHostは、受信するたびに、このServiceProviderに基づいてHttpContextのRequestServicesプロパティとして新しいServiceProviderを作成します.この2つのServiceProviderは親子管理を行います.例として、RequestServicesが返すServiceProviderがConfigureServicesメソッドに従って返すServiceProviderによって作成された場合、登録されたサービスタイプIoobarも認識できるはずですが、なぜエラーが発生するのでしょうか.
この問題を理解するには、このいわゆる「サブServiceProvider」がどのように作成されたのかを知る必要があります.これは、ServiceScopeの概念に関連しています.簡単に言えば、ServiceScopeは、後者のライフサイクルを決定する1つのServiceProviderのパッケージです.ServiceScopeは、サービスとして親ServiceProviderに登録されたServiceScopeFactoryによって作成されます.親ServiceProviderがサブServiceProviderを作成する必要がある場合は、GetServiceメソッドを呼び出して、このServiceScopeFactoryオブジェクト(IServiceScopeFactoryを使用するサービスインタフェース)を取得し、後者を使用してServiceScopeを作成します.このServiceScopeが提供するServiceProviderは、返されるサブServiceProviderです.
しかし、私たちのMyServiceProviderオブジェクトにとって、GetServiceメソッドを呼び出してServiceScopeFactoryオブジェクトを取得しようとすると、実際にはカプセル化されたSerivceProviderに関連付けられたServiceScopeFactoryが取得され、自然に作成された「サブServiceProvider」もMyServiceProviderとは何の関係もありません.
三、この問題をどう解決するか.
問題の根源を知っている以上、自然に解決策があります.ソリューションは複雑ではありません.MyServiceProviderのGetServiceメソッドで、独自のサービス登録に関連するServiceScopeFactoryを返すだけです.この目的のために、次のサービスScopeと対応するサービスScopeFactoryを定義します.

 internal class ServiceScope : IServiceScope
 {
 private MyServiceProvider _serviceProvider;
 
 public ServiceScope(IServiceScope innserServiceScope, Dictionary services)
 {
 _serviceProvider = new MyServiceProvider(innserServiceScope.ServiceProvider, services);
 }
 public IServiceProvider ServiceProvider
 {
 get { return _serviceProvider; }
 }
 
 public void Dispose()
 {
 _serviceProvider.Dispose();
 }
 }
 
 internal class ServiceScopeFactory : IServiceScopeFactory
 {
 private IServiceScopeFactory _innerServiceFactory;
 private Dictionary _services;
 
 public ServiceScopeFactory(IServiceScopeFactory innerServiceFactory, Dictionary services)
 {
 _innerServiceFactory = innerServiceFactory;
 _services = services;
 }
 public IServiceScope CreateScope()
 {
 return new ServiceScope(_innerServiceFactory.CreateScope(), _services);
 }
 }

このほか、MyServiceProviderにコンストラクション関数を追加し、GetServiceメソッドもIServiceScopeFactoryに対応するコードを追加しました.

 public class MyServiceProvider : IServiceProvider, IDisposable
{
 public MyServiceProvider(IServiceProvider innerServiceProvider, Dictionary services)
 {
 _innerServiceProvider = innerServiceProvider;
 _services = services;
 _disposables = new List();
 }
 
 public object GetService(Type serviceType)
 {
 if (serviceType == typeof(IServiceScopeFactory))
 {
 IServiceScopeFactory innerServiceScopeFactory = _innerServiceProvider.GetRequiredService();
 return new ServiceScopeFactory(innerServiceScopeFactory, _services);
 }
 ... 
 }
 ...
 }

以上、このような問題を解決する必要がある友达に役立つことを望んでいます.