ASP.NET Core應(yīng)用中與第三方IoC/DI框架的整合
一、ConfigureServices方法返回的ServiceProvider沒有用!
我們可以通過一個(gè)簡單的實(shí)例來說明這個(gè)問題。我們先定義了如下這個(gè)一個(gè)MyServiceProvider,它實(shí)際上是對(duì)另一個(gè)ServiceProvider的封裝。簡單起見,我們利用一個(gè)字典來保存服務(wù)接口與實(shí)現(xiàn)類型的映射關(guān)系,這個(gè)關(guān)系可以通過調(diào)用Registe方法來注冊(cè)。在提供服務(wù)實(shí)例的GetService方法中,如果提供的服務(wù)類型已經(jīng)被注冊(cè),我們會(huì)創(chuàng)建并返回對(duì)應(yīng)的實(shí)例對(duì)象,否則我們將利用封裝的這個(gè)ServiceProvider來提供服務(wù)。為了確保服務(wù)實(shí)例能夠被正?;厥眨绻?wù)類型實(shí)現(xiàn)了IDisposable接口,我們會(huì)將它添加到通過字段_disposables表示的集合中。當(dāng)MyServiceProvider的Dispose方法被調(diào)用的時(shí)候,提供的這些服務(wù)實(shí)例的Dispose方法會(huì)被調(diào)用。
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();
}
}
我們按照如下的方式在一個(gè)ASP.NET Core應(yīng)用中使用MyServiceProvider。如下面的代碼片斷中,在注冊(cè)的Starup類型中,我們讓ConfigureServices方法返回一個(gè)MyServiceProvider對(duì)象。服務(wù)接口IFoobar和實(shí)現(xiàn)類型Foobar之間的映射注冊(cè)在這個(gè)MyServiceProvider對(duì)象上。在處理請(qǐng)求的時(shí)候,我們利用當(dāng)前HttpContext對(duì)象的RequestServices屬性得到為請(qǐng)求處理提供服務(wù)的ServiceProvider,并試圖利用它得到注冊(cè)的IFoobar服務(wù)。
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 { }
整個(gè)應(yīng)用就這樣簡單,貌似也沒有什么問題,但是我們啟動(dòng)應(yīng)用并利用瀏覽器訪問該應(yīng)用是就會(huì)出現(xiàn)如下所示的錯(cuò)誤。錯(cuò)誤信息表示服務(wù)接口IFoobar尚未被注冊(cè)。

二、原因何在?
我們明明在返回的ServiceProvider注冊(cè)了IFoobar和Foobar之間的映射關(guān)系,為什么RequestServices返回的ServiceProvider說該服務(wù)尚未被注冊(cè)呢?唯一的解釋就是ConfigureServices方法返回的ServiceProvider與HttpContext的RequestServices返回的ServiceProvider根本就不是同一個(gè)。實(shí)際上它們本來就不是同一個(gè)對(duì)象。
ConfigureServices方法返回的ServiceProvider將會(huì)作為WebHost的ServiceProvider,對(duì)于每次接收的請(qǐng)求,WebHost會(huì)根據(jù)這個(gè)ServiceProvider創(chuàng)建一個(gè)新的ServiceProvider來作為HttpContext的RequestServices屬性,這兩個(gè)ServiceProvider具有父子管理。照例說,如果RequestServices返回的ServiceProvider是根據(jù)ConfigureServices方法返回的ServiceProvider創(chuàng)建的,那么它也應(yīng)該能夠識(shí)別注冊(cè)的服務(wù)類型IFoobar,那么為什么依然會(huì)出現(xiàn)錯(cuò)誤呢?
要了解這個(gè)問題,就需要知道這個(gè)所謂的“子ServiceProvider”是如何被創(chuàng)建出來的,這其中涉及到ServiceScope的概念。簡單來說,ServiceScope是對(duì)一個(gè)ServiceProvider的封裝,前者決定后者的生命周期。ServiceScope由ServiceScopeFactory創(chuàng)建,后者以一個(gè)服務(wù)的形式注冊(cè)到“父ServiceProvider”上面。當(dāng)“父ServiceProvider”需要?jiǎng)?chuàng)建“子ServiceProvider”的時(shí)候,它會(huì)調(diào)用GetService方法得到這個(gè)ServiceScopeFactory對(duì)象(采用的服務(wù)接口為IServiceScopeFactory),并利用后者創(chuàng)建一個(gè)ServiceScope,這個(gè)ServiceScope提供的ServiceProvider就是返回的“子ServiceProvider”。
但是對(duì)于我們的MyServiceProvider對(duì)象來說,當(dāng)調(diào)用它的GetService方法試圖獲取ServiceScopeFactory對(duì)象的時(shí)候,獲取的實(shí)際上是被封裝的那個(gè)SerivceProvider關(guān)聯(lián)的ServiceScopeFactory,那么很自然創(chuàng)建的“子ServiceProvider”也與MyServiceProvider沒有什么關(guān)系。
三、如何解決這個(gè)問題?
既然我們知道了問題的根源,我們自然就有了解決方案。解決方案并不復(fù)雜,我們只需要MyServiceProvider的GetService方法返回反映其自身服務(wù)注冊(cè)相關(guān)的ServiceScopeFactory。為此我們定義了如下一個(gè)ServiceScope和對(duì)應(yīng)的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);
}
}
除此之外,我們?yōu)镸yServiceProvider添加了一個(gè)構(gòu)造函數(shù),GetService方法也針對(duì)IServiceScopeFactory添加了相應(yīng)的代碼。
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);
}
...
}
...
}
以上分享,希望能對(duì)需要解決這樣問題的朋友有所幫助!
相關(guān)文章
.NET獲取枚舉DescriptionAttribute描述信息性能改進(jìn)的多種方法
這篇文章主要介紹了.NET獲取枚舉DescriptionAttribute描述信息性能改進(jìn)的多種方法 的相關(guān)資料,需要的朋友可以參考下2016-01-01
基于.net4.0實(shí)現(xiàn)IdentityServer4客戶端JWT解密
這篇文章主要為大家詳細(xì)介紹了基于.net4.0實(shí)現(xiàn)IdentityServer4客戶端JWT解密,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-09-09
ASP.net?Core微信平臺(tái)開發(fā)配置Token
這篇文章主要為大家介紹了ASP.net?Core微信平臺(tái)開發(fā)配置Token有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
Asp.net中處理一個(gè)站點(diǎn)不同Web應(yīng)用共享Session的問題
Asp.net中處理一個(gè)站點(diǎn)不同Web應(yīng)用共享Session的問題...2006-09-09
.net decimal保留指定的小數(shù)位數(shù)(不四舍五入)
大家都知道decimal保留指定位數(shù)小數(shù)的時(shí)候,.NET自帶的方法都是四舍五入的。那么如何讓decimal保留指定位數(shù)小數(shù)的時(shí)候不四舍五入呢,下面通過這篇文中的示例代碼來一起看看吧。2016-12-12
VS2010中呈現(xiàn)控件時(shí)出錯(cuò)的解決方法
如何解決“呈現(xiàn)控件時(shí)出錯(cuò)”的問題,這篇文章主要介紹了VS2010中出現(xiàn)"呈現(xiàn)控件時(shí)出錯(cuò)"問題的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01

