ASP.NET Core应用中与第三方IoC/DI框架整合的示例分析
创始人
2024-11-28 02:01:57
0次

一、ConfigureServices方法返回的ServiceProvider没有用!

我们可以通过一个简单的实例来说明这个问题。我们先定义了如下这个一个MyServiceProvider,它实际上是对另一个ServiceProvider的封装。简单起见,我们利用一个字典来保存服务接口与实现类型的映射关系,这个关系可以通过调用Registe方法来注册。在提供服务实例的GetService方法中,如果提供的服务类型已经被注册,我们会创建并返回对应的实例对象,否则我们将利用封装的这个ServiceProvider来提供服务。为了确保服务实例能够被正常回收,如果服务类型实现了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对象。服务接口IFoobar和实现类型Foobar之间的映射注册在这个MyServiceProvider对象上。在处理请求的时候,我们利用当前HttpContext对象的RequestServices属性得到为请求处理提供服务的ServiceProvider,并试图利用它得到注册的IFoobar服务。

 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 { }

整个应用就这样简单,貌似也没有什么问题,但是我们启动应用并利用浏览器访问该应用是就会出现如下所示的错误。错误信息表示服务接口IFoobar尚未被注册。

ASP.NET Core应用中与第三方IoC/DI框架整合的示例分析

二、原因何在?

我们明明在返回的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 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);
 }
 ... 
 }
 ...
 }

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...