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); } ... } ... }
以上分享,希望能對需要解決這樣問題的朋友有所協助!