ASP.NET Core應用程式中與第三方IoC/DI框架的整合

巴扎黑
發布: 2017-04-17 10:44:52
原創
1704 人瀏覽過

ASP.NET Core應用程式中,針對第三方DI框架的整合可以透過在定義Startup類型的ConfigureServices方法傳回一個ServiceProvider來實現。但並不是那麼容易的,下面透過實例跟大家分享一下

一、ConfigureServices方法回傳的ServiceProvider沒有用!

##我們可以透過一個簡單的實例來說明這個問題。我們先定義如下這個一個MyServiceProvider,它其實是另一個ServiceProvider的封裝。簡單起見,我們利用一個字典來保存服務介面與實作類型的映射關係,這個關係可以透過呼叫Registe方法來註冊。在提供服務實例的GetService方法中,如果提供的服務類型已經被註冊,我們會建立並傳回對應的實例對象,否則我們將利用封裝的這個ServiceProvider來提供服務。為了確保服務實例能夠被正常回收,如果服務類型實作了IDisposable接口,我們會將它加入到透過欄位_disposables表示的集合中。當MyServiceProvider的Dispose方法被呼叫的時候,提供的這些服務實例的Dispose方法會被呼叫。


 public class MyServiceProvider : IServiceProvider, IDisposable
 {
 private IServiceProvider _innerServiceProvider;
 private Dictionary<Type, Type> _services;
 private List<IDisposable> _disposables;
 
 public MyServiceProvider(IServiceProvider innerServiceProvider)
 {
 _innerServiceProvider = innerServiceProvider;
 this._services = new Dictionary<Type, Type>();
 _disposables = new List<IDisposable>();
 }
 
 
 public MyServiceProvider Register<TFrom, TTo>() 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物件。服務介面IFoobar和實作類型Foobar之間的映射註冊在這個MyServiceProvider物件上。在處理請求的時候,我們利用目前HttpContext物件的RequestServices屬性得到為請求處理提供服務的ServiceProvider,並試圖利用它來得到註冊的IFoobar服務。


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

整個應用程式就這樣簡單,似乎也沒有什麼問題,但是我們啟動應用程式並利用瀏覽器存取應用程式是就會出現如下所示的錯誤。錯誤訊息表示服務介面IFoobar尚未被註冊。

二、原因何在?

我們明明在傳回的ServiceProvider註冊了IFoobar和Foobar之間的對應關係,為什麼RequestServices回傳的ServiceProvider說該服務尚未被註冊呢?唯一的解釋就是ConfigureServices方法回傳的ServiceProvider與HttpContext的RequestServices回傳的ServiceProvider根本就不是同一個。實際上它們本來就不是同一個物件。

ConfigureServices方法傳回的ServiceProvider將會作為WebHost的ServiceProvider,對於每次接收的請求,WebHost會根據這個ServiceProvider建立一個新的ServiceProvider來作為HttpContext的RequestServices屬性,這兩個ServiceProvider具有父子管理。照例說,如果RequestServices回傳的ServiceProvider是根據ConfigureServices方法傳回的ServiceProvider所建立的,那麼它也應該能夠辨識註冊的服務類型IFoobar,那麼為什麼還是會出現錯誤呢?

要了解這個問題,就需要知道這個所謂的「子ServiceProvider」是如何被創建出來的,這其中涉及ServiceScope的概念。簡單來說,ServiceScope是對一個ServiceProvider的封裝,前者決定後者的生命週期。 ServiceScope由ServiceScopeFactory創建,以服務的形式註冊到「父ServiceProvider」上面。當「父ServiceProvider」需要建立「子ServiceProvider」的時候,它會呼叫GetService方法得到這個ServiceScopeFactory物件(採用的服務介面為IServiceScopeFactory),並利用後者建立一個ServiceScope,這個ServiceScope提供的ServiceProvider就是一個傳回的「子ServiceProvider」。

但是對於我們的MyServiceProvider物件來說,當呼叫它的GetService方法試圖取得ServiceScopeFactory物件的時候,取得的實際上是被封裝的那個SerivceProvider關聯的ServiceScopeFactory,那麼很自然建立的「子ServiceProvider 」也與MyServiceProvider沒有什麼關係。

三、如何解決這個問題?

既然我們知道了問題的根源,我們自然就有了解決方案。解決方案並不複雜,我們只需要MyServiceProvider的GetService方法傳回反映其自身服務註冊相關的ServiceScopeFactory。為此我們定義瞭如下一個ServiceScope和對應的ServiceScopeFactory。


 internal class ServiceScope : IServiceScope
 {
 private MyServiceProvider _serviceProvider;
 
 public ServiceScope(IServiceScope innserServiceScope, Dictionary<Type, Type> 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<Type, Type> _services;
 
 public ServiceScopeFactory(IServiceScopeFactory innerServiceFactory, Dictionary<Type, Type> 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<Type, Type> services)
 {
 _innerServiceProvider = innerServiceProvider;
 _services = services;
 _disposables = new List<IDisposable>();
 }
 
 public object GetService(Type serviceType)
 {
 if (serviceType == typeof(IServiceScopeFactory))
 {
 IServiceScopeFactory innerServiceScopeFactory = _innerServiceProvider.GetRequiredService<IServiceScopeFactory>();
 return new ServiceScopeFactory(innerServiceScopeFactory, _services);
 }
 ... 
 }
 ...
 }
登入後複製

以上分享,希望能對需要解決這樣問題的朋友有幫助!

以上是ASP.NET Core應用程式中與第三方IoC/DI框架的整合的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板