你的位置:首页 > 软件开发 > ASP.net > 深入剖析“依赖注入”在ASP.NET Core管道中应用[上]:两个不同的ServiceProvider

深入剖析“依赖注入”在ASP.NET Core管道中应用[上]:两个不同的ServiceProvider

发布时间:2016-11-24 08:00:05
我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点。由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内容相对分散和零碎,我们有必要针对这个主题作一个归纳性的介绍。采用依赖注入的服务均 ...

深入剖析“依赖注入”在ASP.NET Core管道中应用[上]:两个不同的ServiceProvider

我们一致在说 get='_blank'>ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点。由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内容相对分散和零碎,我们有必要针对这个主题作一个归纳性的介绍。采用依赖注入的服务均由某个ServiceProvider来提供,但是在ASP.NET Core管道涉及到两个不同的ServiceProvider,其中一个是在管道成功构建后创建并绑定到WebHost上的ServiceProvider,对应着WebHost的Services属性。另一个ServiceProvider则是在管道处理每个请求时即时创建的,它绑定当表示当前请求上下文上,对应着HttpContext的RequestServices属性,两个ServiceProvider之间存在着父子关系。[本文已经同步到《ASP.NET Core框架揭秘》之中]

目录

实例证明

我们上面仅仅从理论层面解释了为什么针对每次请求所使用的ServiceProvider都不相同,接下来我们可以通过实例演示的方式来证实这个推论是成立的。我们在一个控制台应用中编写了如下的代码来启动一个ASP.NET Core应用。我们以不同的生命周期模式(Singleton、Scoped和Transient)之注册三个服务,具体的服务类型都实现了IDisposable接口,而实现的Dispose方**在控制台上打印相应的文字指示那个类型的Dispose方法被执行了。通过调用Configure方法注册的中间件会利用从当前HttpContext获取的ServiceProvider来提供三个对象的服务对象。

  1: public class Program
  2: {
  3:     public static void Main()
  4:     {
  5:         new WebHostBuilder()
  6:             .ConfigureLogging(loggerFactory=>loggerFactory.AddConsole())
  7:             .UseKestrel()
  8:             .ConfigureServices(svcs=>svcs
  9:                 .AddSingleton<IFoo, Foo>()
 10:                 .AddScoped<IBar, Bar>()
 11:                 .AddTransient<IBaz, Baz>())
 12:             .Configure(app => app.Run(async context =>{
 13:                 context.RequestServices.GetService<IFoo>();
 14:                 context.RequestServices.GetService<IBar>();
 15:                 context.RequestServices.GetService<IBaz>();
 16:                 await context.Response.WriteAsync("End");
 17:             }))
 18:             .Build()
 19:             .Run();
 20:     } 
 21: }
 22:  
 23: public interface IFoo {}
 24: public interface IBar {}
 25: public interface IBaz {}
 26: public class ServiceBase : IDisposable
 27: {
 28:     public void Dispose()
 29:     {
 30:         Console.WriteLine($"{this.GetType().Name}.Dispose()...");
 31:     }
 32: }
 33: public class Foo : ServiceBase, IFoo {}
 34: public class Bar : ServiceBase, IBar {}
 35: public class Baz : ServiceBase, IBaz {}

由于我们调用 WebHostBuilder的ConfigureLogging方法添加了ConsoleLoggerProvider,所以管道在开始和结束请求的时候会在当前控制台上写入相应的日志。启动应用之后,我们利用浏览器向默认的监听地址连续发送两次请求后,控制台上将会产生如下所示的输出结果。这样的输出结果表明:对于当前请求处理过程中获取的非Sington服务对象都会请求处理结束之后被自动回收。

  1: info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
  2:       Request starting HTTP/1.1 GET http://localhost:5000/
  3: Baz.Dispose()...
  4: Bar.Dispose()...
  5: info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
  6:       Request finished in 74.9439ms 200
  7:  
  8: info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
  9:       Request starting HTTP/1.1 GET http://localhost:5000/
 10: Baz.Dispose()...
 11: Bar.Dispose()...
 12: info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
 13:       Request finished in 0.8272ms 200

两个ServiceProvider具有“父子”关系

回到前面提到的第二个问题,处理每个请求创建的ServiceProvider和管道构建成功时创建的ServiceProvider(对应WebHost的Services属性)之间具有怎样的关系,其实两者之间的关系很简单,是“父子”关系。下图不仅仅体现了这两种类型的ServiceProvider各自具有的生命周期,同时也体现了它们之间的关系。WebHost的生命周期也就是整个应用的生命周期,所以WebHost的Services属性返回的ServiceProvider是一个全局单例对象。当WebHost随着其Dispose方法被调用而被关闭时,它会调用ServiceProvider的Dispose方法。

深入剖析“依赖注入”在ASP.NET Core管道中应用[上]:两个不同的ServiceProvider

ASP.NET Core管道针对每个请求的处理都在一个全新的HTTP上下文(HttpContext)中进行,提供请求处理所需服务的ServiceProvider与当前上下文绑定在一起,通过HttpContext对象的RequestServices属性返回。由于这个ServiceProvider将WebHost的ServiceProvider作为“父亲” ,所以之前添加的所有服务注册对于它来说依然有效。当前请求一旦结束,当前HttpContext自然 “寿终正寝” ,与之关联的ServiceProvider也随之被回收释放。

ServiceProvidersFeature特性

在了解了两种类型的ServiceProvider各种具有的生命周期和相互关系之后,我们需要了解这个为请求处理提供服务的ServiceProvider是如何被创建,又是如何被回收释放的。对作为默认HttpContext的DefaultHttpContext对象来说,它的RequestServices属性返回的ServiceProvider来源于一个名为ServiceProvidersFeature的特性。所谓的ServiceProvidersFeature特性是对所有实现了IServiceProvidersFeature接口的类型以及对应对象的统称。如下面的代码片段所示,这个接口具有一个唯一属性RequestServices正好用于返回和设置这个ServiceProvider。

  1: public interface IServiceProvidersFeature
  2: {
  3:     IServiceProvider RequestServices { get; set; }
  4: }

ASP.NET Core默认使用的ServiceProvidersFeature是一个类型为RequestServicesFeature的对象,如下所示的代码片段体现了它提供ServiceProvider的逻辑。在创建一个RequestServicesFeature对象的时候,我们需要提供一个根据某个ServiceProvider创建 ServiceScopeFactory对象,它所提供的ServiceProvider就是根据这个ServiceScopeFactory提供的ServiceScope对象创建的。我们根据根据提供的代码可知针对这个属性的多次调用返回的实际上是同一个ServiceProvider。RequestServicesFeature还是实现IDisposable接口,并在实现的Dispose放过中释放了这个ServiceScope,我们知道此举实际上是为了实现对提供的这个ServiceProvider实施回收。

  1: public class RequestServicesFeature : IServiceProvidersFeature, IDisposable
  2: {
  3:     private IServiceScopeFactory     _scopeFactory;
  4:     private IServiceProvider         _requestServices;
  5:     private IServiceScope            _scope;
  6:     private bool                     _requestServicesSet;
  7:  
  8:     public RequestServicesFeature(IServiceScopeFactory scopeFactory)
  9:     {
 10:         _scopeFactory = scopeFactory;
 11:     }
 12:  
 13:     public IServiceProvider RequestServices
 14:     {
 15:         get
 16:         {
 17:             if (!_requestServicesSet)
 18:             {
 19:                 _scope = _scopeFactory.CreateScope();
 20:                 _requestServices = _scope.ServiceProvider;
 21:                 _requestServicesSet = true;
 22:             }
 23:             return _requestServices;
 24:         }
 25:  
 26:         set
 27:         {
 28:             _requestServices = value;
 29:             _requestServicesSet = true;
 30:         }
 31:     }
 32:  
 33:     public void Dispose()
 34:     {
 35:         _scope?.Dispose();
 36:         _scope = null;
 37:         _requestServices = null;
 38:     }
 39: }

RequestServicesContainerMiddleware中间件

那么这个RequestServicesFeature特性又是如何被添加到当前HttpContext的特性集合中的呢?这实际上又涉及到一个名为RequestServicesContainerMiddleware的中间件。我们在创建这个中间件的时候需要提供一个ServiceScopeFactory,该中间件会在Invoke方法被执行的时候根据它创建一个RequestServicesFeature对象,并将其添加到当前HttpContext的特性集合中。当后续的请求处理结束之后,添加的这个RequestServicesFeature对象会被回收释放,并从HttpContext的特性集合中去除。实际上HttpContext的RequestServices返回的ServiceProvider就是在这里被回收释放的。

  1: public class RequestServicesContainerMiddleware
  2: {
  3:     private readonly RequestDelegate     _next;
  4:     private IServiceScopeFactory         _scopeFactory;
  5:  
  6:     public RequestServicesContainerMiddleware(RequestDelegate next, IServiceScopeFactory scopeFactory)
  7:     {        
  8:         _scopeFactory     = scopeFactory;
  9:         _next             = next;
 10:     }
 11:  
 12:     public async Task Invoke(HttpContext httpContext)
 13:     {           
 14:  
 15:         var existingFeature = httpContext.Features.Get<IServiceProvidersFeature>();
 16:         if (existingFeature?.RequestServices != null)
 17:         {
 18:             await _next.Invoke(httpContext);
 19:             return;
 20:         }
 21:  
 22:         using (var feature = new RequestServicesFeature(_scopeFactory))
 23:         {
 24:             try
 25:             {
 26:                 httpContext.Features.Set<IServiceProvidersFeature>(feature);
 27:                 await _next.Invoke(httpContext);
 28:             }
 29:             finally
 30:             {
 31:                 httpContext.Features.Set(existingFeature);
 32:             }
 33:         }
 34:     }
 35: }

AutoRequestServicesStartupFilter

RequestServicesContainerMiddleware中间件的注册最终通过一个StartupFilter对象来完成的,它的类型就是具有如下定义的AutoRequestServicesStartupFilter。对于其Configure方法返回的这个Action<IApplicationBuilder>对象来说,它在注册这个中间件的时候并没有明确之定义一个具体的ServiceScopeFactory对象,那么毫无疑问该中间件使用的ServiceScopeFactory就是根据WebHost的ServiceProvider提供的。WebHost的ServiceProvider提供了一个ServiceScopeFactory,而HttpContext的ServiceProvider又是根据这个ServiceScopeFactory提供的ServiceScope创建的,这两个ServiceProvider之间的父子关系就是采用形式确立的。

  1: public class AutoRequestServicesStartupFilter : IStartupFilter
  2: {
  3:     public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
  4:     {
  5:         return app =>
  6:         {
  7:             app.UseMiddleware<RequestServicesContainerMiddleware>();
  8:             next(app);
  9:         };
 10:     }
 11: }

在WebHostBuilder创建WebHost之前,它会注册一系**保后续的管道能够正常构建并处理请求所必须的服务,这其中就包括这个AutoRequestServicesStartupFilter。综上所述,通过HttpContext的RequestServices属性返回的一个用于提供请求处理过程所需服务的ServiceProvider,这个ServiceProvider的创建和回收释放按是通过一个特性(RequestServicesFeature)、一个中间件(RequestServicesContainerMiddleware)和一个StartupFilter(AutoRequestServicesStartupFilter)相互协作完成的。

深入剖析“依赖注入”在ASP.NET Core管道中应用[上]:两个不同的ServiceProvider

我们知道注册服务具有三种生命周期模式(Singleton、Scoped和Transient)。由于为请求处理提供所需服务的ServiceProvider是基于当前请求上下文的,所以这三种生命周期模式在ASP.NET Core应用中体现了服务实例的复用等级。具体来说,Singleton服务在整个应用生命周期中复用,Scoped服务仅在当前请求上下文中复用,而Transient服务则不能被复用,


原标题:深入剖析“依赖注入”在ASP.NET Core管道中应用[上]:两个不同的ServiceProvider

关键词:ASP.NET

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。