我們知道整個ASP.NET Core建立在以ServiceCollection/ServiceProvider為核心的DI框架上,它甚至提供了擴展點使我們可以與第三方DI框架進行整合。對此比較了解的讀者朋友應該很清楚,針對第三方DI框架的整合可以通過在定義Startup類型的ConfigureServices方法返回一個ServiceProvider來實現。但是真的有這麼簡單嗎?
我們可以通過一個簡單的實例來說明這個問題。我們先定義了如下這個一個MyServiceProvider,它實際上是對另一個ServiceProvider的封裝。簡單起見,我們利用一個字典來保存服務接口與實現類型的映射關系,這個關系可以通過調用Registe方法來注冊。在提供服務實例的GetService方法中,如果提供的服務類型已經被注冊,我們會創建並返回對應的實例對象,否則我們將利用封裝的這個ServiceProvider來提供服務。為了確保服務實例能夠被正常回收,如果服務類型實現了IDisposable接口,我們會將它添加到通過字段_disposables表示的集合中。當MyServiceProvider的Dispose方法被調用的時候,提供的這些服務實例的Dispose方法會被調用。
1: public class MyServiceProvider : IServiceProvider, IDisposable
2: {
3: private IServiceProvider _innerServiceProvider;
4: private Dictionary<Type, Type> _services;
5: private List<IDisposable> _disposables;
6:
7: public MyServiceProvider(IServiceProvider innerServiceProvider)
8: {
9: _innerServiceProvider = innerServiceProvider;
10: this._services = new Dictionary<Type, Type>();
11: _disposables = new List<IDisposable>();
12: }
13:
14:
15: public MyServiceProvider Register<TFrom, TTo>() where TTo: TFrom, new()
16: {
17: _services[typeof(TFrom)] = typeof(TTo);
18: return this;
19: }
20:
21: public object GetService(Type serviceType)
22: {
23: Type implementation;
24: if (_services.TryGetValue(serviceType, out implementation))
25: {
26: object service = Activator.CreateInstance(implementation);
27: IDisposable disposbale = service as IDisposable;
28: if (null != disposbale)
29: {
30: _disposables.Add(disposbale);
31: }
32: return service;
33: }
34: return _innerServiceProvider.GetService(serviceType);
35: }
36:
37: public void Dispose()
38: {
39: (_innerServiceProvider as IDisposable)?.Dispose();
40: foreach (var it in _disposables)
41: {
42: it.Dispose();
43: }
44: _disposables.Clear();
45: }
46: }
我們按照如下的方式在一個ASP.NET Core應用中使用MyServiceProvider。如下面的代碼片斷中,在注冊的Starup類型中,我們讓ConfigureServices方法返回一個MyServiceProvider對象。服務接口IFoobar和實現類型Foobar之間的映射注冊在這個MyServiceProvider對象上。在處理請求的時候,我們利用當前HttpContext對象的RequestServices屬性得到為請求處理提供服務的ServiceProvider,並試圖利用它得到注冊的IFoobar服務。
1: public class Program
2: {
3: public static void Main(string[] args)
4: {
5: new WebHostBuilder()
6: .UseKestrel()
7: .UseStartup<Startup>()
8: .Build()
9: .Run();
10: }
11: }
12:
13: public class Startup
14: {
15: public IServiceProvider ConfigureServices(IServiceCollection services)
16: {
17: return new MyServiceProvider(services.BuildServiceProvider())
18: .Register<IFoobar, Foobar>();
19: }
20:
21: public void Configure(IApplicationBuilder app)
22: {
23: app.UseDeveloperExceptionPage()
24: .Run(async context => await context.Response.WriteAsync(context.RequestServices.GetRequiredService<IFoobar>().GetType().Name));
25: }
26: }
27: public interface IFoobar { }
28: public class Foobar : IFoobar { }
整個應用就這樣簡單,貌似也沒有什麼問題,但是我們啟動應用並利用浏覽器訪問該應用是就會出現如下所示的錯誤。錯誤信息表示服務接口IFoobar尚未被注冊。
我們明明在返回的ServiceProvider注冊了IFoobar和Foobar之間的映射關系,為什麼RequestServices返回的ServiceProvider說該服務尚未被注冊呢?唯一的解釋就是ConfigureServices方法返回的ServiceProvider與HttpContext的RequestServices返回的ServiceProvider根本就不是同一個。實際上它們本來就不是同一個對象。
我在《從兩個不同的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。
1: internal class ServiceScope : IServiceScope
2: {
3: private MyServiceProvider _serviceProvider;
4:
5: public ServiceScope(IServiceScope innserServiceScope, Dictionary<Type, Type> services)
6: {
7: _serviceProvider = new MyServiceProvider(innserServiceScope.ServiceProvider, services);
8: }
9: public IServiceProvider ServiceProvider
10: {
11: get { return _serviceProvider; }
12: }
13:
14: public void Dispose()
15: {
16: _serviceProvider.Dispose();
17: }
18: }
19:
20: internal class ServiceScopeFactory : IServiceScopeFactory
21: {
22: private IServiceScopeFactory _innerServiceFactory;
23: private Dictionary<Type, Type> _services;
24:
25: public ServiceScopeFactory(IServiceScopeFactory innerServiceFactory, Dictionary<Type, Type> services)
26: {
27: _innerServiceFactory = innerServiceFactory;
28: _services = services;
29: }
30: public IServiceScope CreateScope()
31: {
32: return new ServiceScope(_innerServiceFactory.CreateScope(), _services);
33: }
34: }
除此之外,我們為MyServiceProvider添加了一個構造函數,GetService方法也針對IServiceScopeFactory添加了相應的代碼。
1: public class MyServiceProvider : IServiceProvider, IDisposable
2: {
3: public MyServiceProvider(IServiceProvider innerServiceProvider, Dictionary<Type, Type> services)
4: {
5: _innerServiceProvider = innerServiceProvider;
6: _services = services;
7: _disposables = new List<IDisposable>();
8: }
9:
10: public object GetService(Type serviceType)
11: {
12: if (serviceType == typeof(IServiceScopeFactory))
13: {
14: IServiceScopeFactory innerServiceScopeFactory = _innerServiceProvider.GetRequiredService<IServiceScopeFactory>();
15: return new ServiceScopeFactory(innerServiceScopeFactory, _services);
16: }
17: ...
18: }
19: ...
20: }