学习ASP.NET Core,怎能不了解请求处理管道[1]: 中间件究竟是个什么东西?

ASP.NET Core管道虽然在结构组成上显得非常简单,但是在具体实现上却涉及到太多的对象,所以我们在 “通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程”(上篇中篇下篇) 中围绕着一个经过极度简化的模拟管道讲述了真实管道构建的方式以及处理HTTP请求的流程。在本系列 中,我们会还原构建模拟管道时可以舍弃和改写的部分,向读者朋友们呈现一个真是的HTTP请求处理管道。 ASP.NET Core 的请求处理管道由一个服务器与一组有序排列的中间件构成,前者仅仅完成请求监听、接收和响应这些与底层网络相关的工作,至于请求接收之后和响应之前的所有工作都交给中间件来完成。ASP.NET Core的中间件通过一个类型Func<RequestDelegate, RequestDelegate>的委托对象来表示,而RequestDelegate也是一个委托,它代表一项请求处理任务。

一、RequestDelegate

服务器接受到抵达的HTTP请求之后会构建一个描述当前请求的原始上下文,服务器的类型决定了这个原始上下文的类型,比如在我们模拟管道默认采用的HttpListenerServer由于采用HttpListener来监听、接收并响应请求,所以它对应的原始上下文是一个HttpListenerContext对象。但是对于管道的后续部分,即由注册的中间件构建的链表,它们需要采用统一的方式来处理请求,所以服务器最终会根据原始的上下文来创建一个抽象的HTTP上下文,后者通过抽象类HttpContext来表示。

我们不仅可以利用这个HttpContext获取描述当前请求的上下文信息,同样可以利用它来实现对响应的控制。针对当前请求的任何处理操作总是在这么一个上下文中进行,所以一项请求处理任务完全可以抽象成一个类型Func<HttpContext,Task>的委托来表示,实际上具有如下定义的RequestDelegate委托具有类似的定义。

   1: public delegate Task RequestDelegate(HttpContext context); 

每个中间件都承载着独立的请求处理任务,它本质上也体现了在当前HttpContext下针对请求的处理操作,那么为什么中间件不直接通过一个RequestDelegate对象来表示,而是表示为一个类型为Func<RequestDelegate, RequestDelegate>的委托对象呢?原因很简单,中间件并不孤立地存在,所有注册的中间件最终会根据注册的先后顺序组成一个链表,每个中间件不仅仅需要完成各自的请求处理任务外,还需要驱动链表中的下一个中间件。

如上图所示,对于一个由多个Func<RequestDelegate, RequestDelegate>对象组成的中间链表来说,某个中间件会将后一个Func<RequestDelegate, RequestDelegate>对象的返回值作为输入,而自身的返回值则作为前一个中间件的输入。某个中间件执行之后返回的RequestDelegate对象不仅仅体现了自身对请求的处理操作,而是体现了包含自己和后续中间件一次对请求的处理。那么对于第一个中间件来说,它执行后返回的RequestDelegate对象实际上体现了整个应用对请求的处理逻辑。

二、 HttpContext

对当前上下文的抽象解除了管道对具体服务器类型的依赖, 这使我们可以为ASP.NET Core应用自由地选择承载(Hosting)方式,而不是像传统的ASP.NET应用一样只能寄宿在IIS之中。抽象HTTP上下文的目的是为了实现对请求处理流程的抽象,只有这样我们才能将针对请求的某项操作体现在一个标准的中间件上,有了这个这个标准化的中间件才有所谓的请求处理管道。

ASP.NET Core通过具有如下所示的HttpContext类来表示这么一个抽象的HTTP上下文。对于一个HttpContext对象来说,它的核心体现在用于描述请求和响应的Request和Response属性之上。除此之外,我们还可以通过它获取与当前请求相关的其他上下文信息,比如用来控制用户认证的AuthenticationManager对象和代表当前请求用户的ClaimsPrincipal对象,以及描述当前HTTP连接的ConnectionInfo对象和用于控制WebSocket的WebSocketManager。我们可以获取并控制当前会话,也可以获取或者设置调试追踪的ID。

   1: public abstract class HttpContext
   2: {
   3:  
   4:     public abstract HttpRequest     Request { get; }
   5:     public abstract HttpResponse    Response { get; }
   6:  
   7:     public abstract AuthenticationManager            Authentication { get; }
   8:     public abstract ClaimsPrincipal                  User { get; set; }
   9:     public abstract ConnectionInfo                   Connection { get; } 
  10:     public abstract WebSocketManager                 WebSockets { get; } 
  11:     public abstract ISession                         Session { get; set; } 
  12:     public abstract string                           TraceIdentifier { get; set; }
  13:     public abstract CancellationToken                RequestAborted { get; set; }  
  14:     public abstract IDictionary<object, object>      Items { get; set; }  
  15:  
  16:     public abstract IServiceProvider                RequestServices { get; set; }
  17:     public abstract IFeatureCollection              Features { get; }
  18: }

当需要中指对请求的处理时,我们可以通过为RequestAborted属性设置一个CancellationToken对象从而将终止通知发送给管道。如果需要对整个管道共享一些与当前上下文相关的数据,我们可以将它保存在通过Items属性表示的字典中。我们一再提到依赖注入被广泛地应用ASP.NET Core管道中,HttpContext的RequestServices属性返回的根据在应用启动时注册的服务而创建的ServiceProvider。只要相应的服务被预先注册到指定的服务接口上,我们就可能利用这个ServiceProvider根据这个接口得到对应的服务对象。

   1: public abstract class HttpRequest
   2: {
   3:     public abstract HttpContext                    HttpContext { get; }
   4:     public abstract string                         Method { get; set; }
   5:     public abstract string                         Scheme { get; set; }
   6:     public abstract bool                           IsHttps { get; set; }
   7:     public abstract HostString                     Host { get; set; }
   8:     public abstract PathString                     PathBase { get; set; }
   9:     public abstract PathString                     Path { get; set; }
  10:     public abstract QueryString                    QueryString { get; set; }
  11:     public abstract IQueryCollection               Query { get; set; }
  12:     public abstract string                         Protocol { get; set; }
  13:     public abstract IHeaderDictionary              Headers { get; } >
  14:     public abstract IRequestCookieCollection       Cookies { get; set; }
  15:     public abstract string                         ContentType { get; set; }
  16:     public abstract Stream                         Body { get; set; }
  17:     public abstract bool                           HasFormContentType { get; }
  18:     public abstract IFormCollection                Form { get; set; }
  19:  
  20:     public abstract Task<IFormCollection>         ReadFormAsync(CancellationToken cancellationToken);
  21: }

如上所示的是抽象类HttpRequest是对HTTP请求的描述,它是HttpContext的只读属性Request的返回类型。我们可以利用这个对象获取到描述当前请求的各种相关信息,比如请求的协议(HTTP或者HTTPS)、HTTP方法、地址,也可以获取代表请求的HTTP消息的首部和主体。

在了解了表示请求的抽象类HttpRequest之后,我们再来认识一个与之相对的用于描述响应HttpResponse类型。如下面的代码片断所示,HttpResponse依然是一个抽象类,我们可以通过定义在它之上的属性和方法来控制对请求的响应。从原则上讲,我们对请求的所做的任意类型的响应都可以利用它来说实现。当我们通过表示当前上下文的HttpContext对象得到表示响应的HttpResponse之后,我们不仅仅可以将希望的内容写入响应消息的主体,还可以设置响应状态码以及添加相应的首部。

   1: public abstract class HttpResponse
   2: {
   3:     public abstract HttpContext           HttpContext { get; }
   4:     public abstract int                   StatusCode { get; set; }
   5:     public abstract IHeaderDictionary     Headers { get; }
   6:     public abstract Stream                Body { get; set; }
   7:     public abstract long?                 ContentLength { get; set; }
   8:     public abstract IResponseCookies      Cookies { get; }
   9:     public abstract bool                  HasStarted { get; }
  10:  
  11:     public abstract void OnStarting(Func<object, Task> callback, object state);
  12:     public virtual void OnStarting(Func<Task> callback);
  13:     public abstract void OnCompleted(Func<object, Task> callback, object state);
  14:     public virtual void RegisterForDispose(IDisposable disposable);
  15:     public virtual void OnCompleted(Func<Task> callback);
  16:     public virtual void Redirect(string location);
  17:     public abstract void Redirect(string location, bool permanent);
  18: }

FeatureCollection

HttpContext的另一个只读属性Features返回一组“特性”对象。在ASP.NET Core管道式处理设计中,特性是一个非常重要的概念,特性是实现抽象化HttpContext的途径。具体来说,服务器在接收到请求之后会创建一个由自身类型决定的原始的上下文,管道不仅仅利用这个原始上下文来获取与请求相关的信息,它对请求的最终响应实际上也是通过这个原始上下文来完成的。所以对一个HttpContext对象来说,由它描述的上下文信息不仅仅来源于这个原始的上下文,我们针对HttpContext所做的任何响应操作最终都需要分发给这个原始上下文来完成, 否则是不会生效的。抽象的HttpContext和原始上下文之间的“双向绑定”究竟是如何实现的呢?

这个所谓的“双向绑定”即使其实很简单。当原始上下文被创建出来之后,服务器会将它封装成一系列标准的特性对象,HttpContext正是对这些特性对象的封装。一般来说,这些特性对象所对应的类型均实现了某个预定义的标准接口,接口中不仅仅定义相应的属性来读写原始上下文中描述的信息,还定义了相应的方法来操作原始上下文。HttpContext的属性Features返回的就是这组特性对象的集合,它的返回类型为IFeatureCollection,我们将实现了该接口的类型以及对应的对象统称为FeatureCollection。

   1: public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>
   2: {
   3:     TFeature Get<TFeature>();
   4:     void Set<TFeature>(TFeature instance);
   5:  
   6:     bool       IsReadOnly { get; }
   7:     object     this[Type key] { get; set; }
   8:     int        Revision { get; }
   9:  }

一个FeatureCollection对象本质上就是一个Key和Value分别为Type和Object类型的字段,话句话说,特性对象通过对应的接口类型注册到HttpContext之上。我们通过调用Set方法将一个特性对象针对指定的类型(一般为特性接口)注册到这个字典对象上,并通过Get方法根据注册的类型获取它。特性对象的注册和获取也可以利用定义的索引来完成。如果IsReadOnly属性返回True,我们将不能注册新的特性或者修改已经注册的特性。 整数类型的之都属性Revision可以视为整个FeatureCollection对象的版本,不论是采用何种方式注册新的特性还是修改现有的特性,这个属性的值都将改变。

具有如下定义的FeatureCollection类实现了IFeatureCollection接口,我们默认使用的FeatureCollection就是这么一个类型的对象。FeatureCollection具有两个构造函数重载,默认无参构造函数帮助我们创建一个空的特性集合,另一个构造函数则需要指定一个FeatureCollection对象来提供默认特性。对于采用第二个构造函数重载创建的 FeatureCollection对象来说,当我们通过指定某个特性接口类型试图获取对应的特性对象时,如果对应的特性没有注册到当前FeatureCollection对象上,而是注册到提供默认特性的FeatureCollection对象上,后者将会提供最终的特性。

   1: public class FeatureCollection : IFeatureCollection
   2: {   
   3:     //其他成员
   4:     public FeatureCollection();
   5:     public FeatureCollection(IFeatureCollection defaults);
   6: }

对于FeatureCollection类型来说,它 的IsReadOnly总是返回False,所以它永远是可读可写的。对于调用默认无参构造函数创建的FeatureCollection对象来说,它 的Revision默认返回零。如果我们通过指定另一个FeatureCollection对象为参数调用第二个构造函数来创建一个FeatureCollection对象,前者的Revision属性值将成为后者同名属性的默认值。不论我们采用何种形式(调用Set方法或者索引)添加一个新的特性或者改变了一个已经注册的特性,FeatureCollection对象的Revision属性都将自动递增。上述的这些关于FeatureCollection的特性都体现在如下所示的代码片段中。

   1: FeatureCollection defaults = new FeatureCollection();
   2: Debug.Assert(defaults.Revision == 0);
   3:  
   4: defaults.Set<IFoo>(new Foo());
   5: Debug.Assert(defaults.Revision == 1);
   6:  
   7: defaults[typeof(IBar)] = new Bar();
   8: Debug.Assert(defaults.Revision == 2);
   9:  
  10: FeatureCollection features = new FeatureCollection(defaults);
  11: Debug.Assert(features.Revision == 2);
  12: Debug.Assert(features.Get<IFoo>().GetType() == typeof(Foo));
  13:  
  14: features.Set<IBaz>(new Baz());
  15: Debug.Assert(features.Revision == 3);

DefaultHttpContext

ASP.NET Core默认使用的HttpContext类型为DefaultHttpContext,上面我们介绍的针对描述原始上下文“特性集合”来创建HttpContext的策略就体现在这个类型之上。DefaultHttpContext具有一个如下的构造函数,作为参数的FeatureCollection对象就是这么一个特性集合。

   1: public class DefaultHttpContext : HttpContext
   2: {
   3:     public DefaultHttpContext(IFeatureCollection features);
   4: }

不论是组成管道的中间件还是建立在管道上的应用,在默认的情况下都是利用这个DefaultHttpContext对象来获取当前请求的相关信息,并利用这个对象来控制最终发送的响应。但是DefaultHttpContext对象这个这个过程中仅仅是一个“代理”,针对它的调用(属性或者方法)最终都需要转发给由具体服务器创建的那个原始上下文,在构造函数中指定的这个FeatureCollection对象所代表的特性集合成为了这两个上下文对象进行沟通的唯一渠道。对于定义在DefaultHttpContext中的所有属性,它们几乎都具有一个对应的特性,这些特性都对应着一个接口。表1列出了部分特性接口以及DefaultHttpContext对应的属性。

表1 描述原始HTTP上下文的特性接口


接口

属性

描述

IHttpRequestFeature

Request

获取描述请求的基本信息。

IHttpResponseFeature

Response

控制对请求的响应。

IHttpAuthenticationFeature

AuthenticationManger/User

提供完成用户认证的AuthenticationHandler对象和表示当前用户的ClaimsPrincipal对象

IHttpConnectionFeature

Connection

提供描述当前HTTP连接的基本信息。

IItemsFeature

Items

提供用户存放针对当前请求的对象容器。

IHttpRequestLifetimeFeature

RequestAborted

传递请求处理取消通知和中止当前请求处理。

IServiceProvidersFeature

RequestServices

提供根据服务注册创建的ServiceProvider。

ISessionFeature

Session

提供描述当前会话的Session对象。

IHttpRequestIdentifierFeature

TraceIdentifier

为追踪日志(Trace)提供针对当前请求的唯一标识。

IHttpWebSocketFeature

WebSockets

管理WebSocket


对于上面列出的众多特性接口,我们在后续相关章节中都会涉及到,目前来说我们只需要了解一下两个最重要的特性接口,即表示请求和响应的IHttpRequestFeature和IHttpResponseFeature。从下面给出的代码片断我们不难看出,这两个接口的定义分别与抽象类HttpRequest和HttpResponse具有一致的定义。对于DefaultHttpContext类型来说,它的Request和Response属性分别返回的是一个DefaultHttpRequest和DefaultHttpResponse对象。DefaultHttpRequest和DefaultHttpResponse分别继承自HttpRequest和HttpResponse,它们分别利用这个两个特性实现了从基类继承下来的所有抽象成员。

   1: public interface IHttpRequestFeature
   2: {
   3:     Stream             Body { get; set; }
   4:     IHeaderDictionary  Headers { get; set; }
   5:     string             Method { get; set; }
   6:     string             Path { get; set; }
   7:     string             PathBase { get; set; }
   8:     string             Protocol { get; set; }
   9:     string             QueryString { get; set; }
  10:     string             Scheme { get; set; }
  11: }
  12:  
  13: public interface IHttpResponseFeature
  14: {
  15:     Stream             Body { get; set; }
  16:     bool               HasStarted { get; }
  17:     IHeaderDictionary  Headers { get; set; }
  18:     string             ReasonPhrase { get; set; }
  19:     int                 StatusCode { get; set; }
  20:  
  21:     void OnCompleted(Func<object, Task> callback, object state);
  22:     void OnStarting(Func<object, Task> callback, object state);
  23: }

对于实现请求监听、接收和响应的服务器来说,它们都需要通过实现上面这些特性接口来定义针对性的特性类。如下图所示,当成功接收到请求之后,服务器会创建相应的特性并将它们组合成一个FeatureCollection对象,最后创建出一个DefaultHttpContext对象,我们注册的所有中间件针对这个DefaultHttpContext完成各自的请求处理工作。

HttpContextFactory

在服务器接收到抵达的请求时,它并不会直接利用原始的上下文去创建HttpContext对象,HttpContext在管道中的创建是间接地通过HttpContextFactory来完成的。 HttpContextFactory是对所有实现了IHttpContextFactory接口的所有类型及其对象的统称。如下面的代码片段所示,IHttpContextFactory接口除了定义创建HttpContext对象的Create方法之外,还定义了另一个方法Dispose来释放指定的HttpContext对象。HttpContextFactory类是该接口的默认实现者,由它的Create方法创建并返回的自然是一个DefaultHttpContext对象。

   1: public interface IHttpContextFactory
   2: {
   3:     HttpContext Create(IFeatureCollection featureCollection);
   4:     void Dispose(HttpContext httpContext);
   5: }
   6:  
   7: public class HttpContextFactory : IHttpContextFactory
   8: {    
   9:     //省略其他成员
  10:     public HttpContext Create(IFeatureCollection featureCollection);
  11:     public void Dispose(HttpContext httpContext);
  12: }

综上所述,组成管道的所有中间件在一个标准化的上下文中完整对请求的处理,这个上下文通过抽象类HttpContext表示,ASP.NET Core默认使用的是它的子类DefaultHttpContext。一个DefaultHttpContext对象是根据描述原始上下文的特性集合,每个特性对应的类型都实现了标准的接口,接口IHttpRequestFeature和IHttpResponseFeature分别代表针对请求和响应的特性。HttpContext默认情况下是通过注册的工厂创建的,该工厂通过接口IHttpContextFactory表示,默认使用的HttpContext工厂类型为HttpContextFactory,它也是DefaultHttpContext对象的创建者。

三、ApplicationBuilder

以类型为Func<RequestDelegate, RequestDelegate>的委托对象表示的中间件需要在启动的时候注册到应用程序上。所有注册的中间件最终会转换成一个RequestDelegate类型的委托对象,它们按照注册顺序对请求的处理流程最终体现在对这个委托对象的执行。不论是最终将中间件转换成RequestDelegate对象,还是最初对它们的注册,都是通过一个名为ApplicationBuilder的对象来完成的。

ApplicationBuilder是我们对所有实现了IApplicationBuilder接口的所有类型以及对应对象的统称。接口IApplicationBuilder定义如下,中间件的注册和RequestDelegate对象的生成分别通过调用它的Use和Build方法来完成。除了这两个核心方法,IApplicationBuilder接口还定义了三个属性,其中ApplicationServices返回根据最初服务注册生成的ServiceProvider对象,而ServerFeatures属性返回的FeatureCollection对象是描述Server的特性集合。字典类型的Properties属性用户存储任意自定义的属性,而New方法会根据自己“克隆”出一个新的ApplicationBuilder对象,这两个ApplicationBuilder对象应用具有相同的属性集合。

   1: public interface IApplicationBuilder
   2: {
   3:     IServiceProvider             ApplicationServices { get; set; }
   4:     IFeatureCollection           ServerFeatures { get; }
   5:     IDictionary<string, object>  Properties { get; }
   6:  
   7:     RequestDelegate         Build();
   8:     IApplicationBuilder     New();
   9:     IApplicationBuilder     Use(Func<RequestDelegate, RequestDelegate> middleware);
  10: }

具有如下定义的ApplicationBuilder类型是对IApplicationBuilder接口的默认实现。ApplicationBuilder类型利用一个List<Func<RequestDelegate, RequestDelegate>>对象来保存注册的中间件,所以Use方法只需要将指定的中间件添加到这个列表中即可,而Build方法只需要逆序调用这些注册的中间件对应的Func<RequestDelegate, RequestDelegate>对象就能得到我们需要的RequestDelegate对象。值得一提的是,Build方法实际上在中间件链条的尾部添加了一个额外的中间件,该中间件会负责将响应状态码设置为404,如果我们没有注册一个中间件对请求作最终的响应(这样的中间件将不会试图调用后续中间件),整个管道比较回复一个状态码为404的响应。

   1: public class ApplicationBuilder : IApplicationBuilder
   2: {
   3:     private readonly IList<Func<RequestDelegate, RequestDelegate>> middlewares = new List<Func<RequestDelegate, RequestDelegate>>();
   4:  
   5:     public IDictionary<string, object> Properties { get; }
   6:  
   7:     public IServiceProvider ApplicationServices
   8:     {
   9:         get { return GetProperty<IServiceProvider>("application.Services"); }
  10:         set { SetProperty<IServiceProvider>("application.Services", value); }
  11:     }
  12:  
  13:     public IFeatureCollection ServerFeatures
  14:     {
  15:         get { return GetProperty<IFeatureCollection>("server.Features"); }
  16:     }
  17:  
  18:  
  19:     public ApplicationBuilder(IServiceProvider serviceProvider)
  20:     {
  21:         this.Properties = new Dictionary<string, object>();
  22:         ApplicationServices = serviceProvider;
  23:     }
  24:  
  25:     public ApplicationBuilder(IServiceProvider serviceProvider, object server)
  26:         : this(serviceProvider)
  27:     {
  28:         SetProperty("server.Features", server);
  29:     }
  30:  
  31:     public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
  32:     {
  33:         middlewares.Add(middleware);
  34:         return this;
  35:     }
  36:  
  37:     public IApplicationBuilder New()
  38:     {
  39:         return new ApplicationBuilder(this);
  40:     }
  41:  
  42:     public RequestDelegate Build()
  43:     {
  44:         RequestDelegate app = context =>
  45:         {
  46:             context.Response.StatusCode = 404;
  47:             return Task.FromResult(0);
  48:         };
  49:         foreach (var component in middlewares.Reverse())
  50:         {
  51:             app = component(app);
  52:         }
  53:         return app;
  54: }
  55:  
  56:     private ApplicationBuilder(ApplicationBuilder builder)
  57:     {
  58:         this.Properties = builder.Properties;
  59:     }        
  60:  
  61:     private T GetProperty<T>(string key)
  62:     {
  63:         object value;
  64:         return Properties.TryGetValue(key, out value) ? (T)value : default(T);
  65:     }
  66:  
  67:     private void SetProperty<T>(string key, T value)
  68:     {
  69:         this.Properties[key] = value;
  70:     }
  71: }

通过上面的代码片段我们不难看到,不论是通过ApplicationServices属性返回的ServiceProvider对象,还是通过ServerFeatures属性返回的用于描述Server特性的FeatureCollection对象,它们实际上都保存在通过Properties属性返回字典对象上。ApplicationBuilder具有两个公共构造函数重载,它们具有一个公共的参数,即用来初始化ApplicationServices属性的参数serviceProvider。

一个构造函数具有一个名为server的参数,但是这个参数并不是表示管道使用的服务器,而是承载服务器相关特性的FeatureCollection对象,不过这个参数类型被定义成Object,而不是IFeatureCollection接口。New方法直接调用私有构造函数创建出一个新的ApplicationBuilder对象,这个对象与自己的Properties属性共享同一个字典对象,由于ApplicationServices和ServerFeatures属性的返回值也存放在这个字典对象上,所以New方法得到的ApplicationBuilder对象与自身对象其实是完全等效的。

ApplicationBuilderFactory

ApplicationBuilderFactory是ASP.NET Core它用来创建ApplicationBuilder的工厂,它是对所有实现了接口IApplicationBuilderFactory的所有类型以及对应对象的统称。如下面的代码片段所示,该接口定义了唯一个方法CreateBuilder根据提供的FeatureCollection对象创建出对应的ApplicationBuilder对象,这个FeatureCollection对象正是承载与服务器相关特性的集合。ApplicationBuilderFactory类型是该接口的默认实现者,当CreateBuilder方法被调用的时候,它会直接将构造时提供ServiceProvider对象和serverFeatures参数表示的FeatureCollection对象来创建返回的ApplicationBuilder对象。

   1: public interface IApplicationBuilderFactory
   2: {
   3:     IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures);
   4: }
   5:  
   6: public class ApplicationBuilderFactory : IApplicationBuilderFactory
   7: {
   8:     private readonly IServiceProvider _serviceProvider;
   9:  
  10:     public ApplicationBuilderFactory(IServiceProvider serviceProvider)
  11:     {
  12:         this._serviceProvider = serviceProvider;
  13:     }
  14:  
  15:     public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures)
  16:     {
  17:         return new ApplicationBuilder(_serviceProvider, serverFeatures);
  18:     }
  19: }

中间件类型

虽然中间件最终体现为一个类型为 Func<RequestDelegate, RequestDelegate>的委托对象,但是我们在大部分情况下都会将中间件定义成一个单独的类型。虽然这样的中间件类型不要求实现某个预定义的接口或者继承某个预定义的基类,但是却要遵守几个必要的约定。接下来我们直接如下这个ContentMiddleware类说说一个合法的中间件类型应该如何定义。

   1: public class ContentMiddleare
   2: {
   3:     public RequestDelegate     _next;
   4:     public byte[]         _content;
   5:     public string         _contentType;
   6:  
   7:     public ContentMiddleare(RequestDelegate next, byte[] content, string contentType)
   8:     {
   9:         _next         = next;
  10:         _content      = content;
  11:         _contentType  = contentType;
  12:     }
  13:  
  14:     public async Task Invoke(HttpContext context, ILoggerFactory loggerFactory)
  15:     {
  16:         loggerFactory.CreateLogger<ContentMiddleare>().LogInformation($"Write content ({_contentType})");
  17:         context.Response.ContentType = _contentType;
  18:         await context.Response.Body.WriteAsync(_content,0, _content.Length);
  19:     }
  20: }

如上所示的这个中间件(ContentMiddleware)可以帮助我们将任何类型的内容响应给客户端,它的两个字段_content和_contentType分别代表响应内容和媒体类型(内容类型或者MIME类型),它体现了一个典型中间件类型的定义规则或者约定:

  • 应该定义成实例类,不能定义成静态类。

  • 具有一个有效的公共构造函数。这个构造函数的第一个参数类型必须为RequestDelegate,代表对请求的后续操作(可以视为下一个注册的中间件),至于后续参数的个数和类型则不作要求。

  • 针对请求的处理定义在一个名为Invoke的公共实例方法,其返回类型为Task。该方法的第一个参数类型为HttpContext,代表当前HTTP上下文。我们可以为这个方法定义任意数量和类型的后续参数,当这个方法被执行的时候,系统将会采用依赖注入的方式提供响应的服务来为这个参数赋值。

中间件类型的注册

中间件类型的注册可以通过调用 IApplicationBuilder接口的扩展方法UseMiddleware 和UseMiddleware
< TMiddleware >进行注册。如下面的代码片断所示,除了指定中间件的类型之外,我们还需要按照顺序指定调用目标构造函数的全部或者部分参数。不过不过的参数列表不需要提供作为第一个参数的RequestDelegate,如果仅仅指定了部分参数,缺失的参数将会自动通过ServiceProvider来提供。

   1: public static class UseMiddlewareExtensions
   2: {
   3:     public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args);
   4:     public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args);
   5: }

对于上面定义的这个 ContentMiddleare类型,我们按照如下的方式对它进行了注册。当这个中间件执行的时候,它会响应客户端一张PNG图片。如果客户端是能够支持图片呈现的浏览器,这张图片会直接显示在浏览器上。

   1: new WebHostBuilder()
   2:     .Configure(app=>app.UseMiddleware<ContentMiddleare>(File.ReadAllBytes("girl.png"),"image/png"))
   3: ...

虽然中间件可以定义成任何一个遵循约定的类型,但是中间件自身在ASP.NET Core框架中总是体现为一个类型为Func<RequestDelegate, RequestDelegate>的委托对象,所以上述的这个UseMiddleware方法在执行的时候需要在内部根据注册的中间件类型和指定的参数列表创建这么一个Func<RequestDelegate, RequestDelegate>对象。其中的逻辑并不复杂,它之需要将中间件对象的创建和针对Invoke方法的调用实现在返回的委托对象中就可以了。值得一提的是,针对Invoke方法的调用并没有直接通过反射的方式来实现,而是采用表达式,后者具有更好的性能。在如下所示的代码片段中,我采用最精简的代码模拟了UseMiddleware方法的实现。

   1: public static class WebHostBuilderExtensions
   2: {
   3:     private static MethodInfo GetServiceMethod = typeof(WebHostBuilderExtensions).GetMethod("GetService", BindingFlags.Static | BindingFlags.NonPublic);
   4:  
   5:     public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
   6:     {
   7:         return UseMiddleware2(app, typeof(TMiddleware), args);
   8:     }
   9:  
  10:     public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middlewareType, params object[] args)
  11:     {
  12:         return app.Use(next =>
  13:         {
  14:             return context => {
  15:                 var factory = Compile<object>(middlewareType.GetMethod("Invoke", BindingFlags.Instance | BindingFlags.Public));
  16:                 object middleware = CreateMiddleware(app, middlewareType, next, args);
  17:                 return factory(middleware, context, app.ApplicationServices);
  18:             };              
  19:         });
  20:     }
  21:  
  22:     private static object CreateMiddleware(IApplicationBuilder app, Type middlewareType, RequestDelegate next, params object[] args)
  23:     {
  24:         object[] arguments = new object[args.Length + 1];
  25:         arguments[0] = next;
  26:         args.CopyTo(arguments, 1);
  27:         return ActivatorUtilities.CreateInstance(app.ApplicationServices, middlewareType, arguments);
  28:     }
  29:     
  30:     //将对Invoke方法的调用转换成一个Func<TMiddleware, HttpContext, IServiceProvider, Task>对象
  31:     private static Func<TMiddleware, HttpContext, IServiceProvider, Task> Compile<TMiddleware>(MethodInfo invokeMethod)
  32:     {           
  33:         ParameterExpression middleware = Expression.Parameter(typeof(TMiddleware), "middleware");
  34:         ParameterExpression httpContext = Expression.Parameter(typeof(HttpContext), "httpContext");
  35:         ParameterExpression serviceProvider = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
  36:  
  37:         var arguments = from parameter in invokeMethod.GetParameters()
  38:             select GetArgument(httpContext, serviceProvider, parameter.ParameterType);
  39:  
  40:         Expression instance = middleware;
  41:         if (invokeMethod.DeclaringType != typeof(TMiddleware))
  42:         {
  43:             instance = Expression.Convert(instance, invokeMethod.DeclaringType);
  44:         }
  45:  
  46:         Expression invoke = Expression.Call(instance, invokeMethod, arguments.ToArray());
  47:         return Expression.Lambda<Func<TMiddleware, HttpContext, IServiceProvider, Task>>(invoke, middleware, httpContext, serviceProvider).Compile();
  48:     }
  49:     
  50:     //生成调用Invoke方法的参数表达式
  51:     private static Expression GetArgument(Expression httpContext, Expression serviceProvider, Type parameterType)
  52:     {
  53:         if (parameterType == typeof(HttpContext))
  54:         {
  55:             return httpContext;
  56:         }
  57:         Expression serviceType = Expression.Constant(parameterType, typeof(Type));
  58:         Expression callGetService = Expression.Call(GetServiceMethod, serviceProvider, serviceType);
  59:         return Expression.Convert(callGetService, parameterType);
  60:     }
  61:  
  62:     private static object GetService(IServiceProvider serviceProvider, Type serviceType)
  63:     {
  64:         return serviceProvider.GetService(serviceType);
  65:     }
  66: }

原文地址:http://www.cnblogs.com/artech/p/asp-net-core-real-pipeline-01.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/323618.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot实现用户统一认证、管理

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂”前言现在是&#xff1a;2022年5月25日13:44:16最近和模拟登录杠上了&#xff0c;这不&#xff0c;又来了个需求&#xff0c;还是以这个技术点入手的。需求大概是这样的&#xff1a;为了统…

Mybatis 使用的 9 种设计模式,真是太有用了

转载自 Mybatis 使用的 9 种设计模式&#xff0c;真是太有用了 虽然我们都知道有26个设计模式&#xff0c;但是大多停留在概念层面&#xff0c;真实开发中很少遇到&#xff0c;Mybatis源码中使用了大量的设计模式&#xff0c;阅读源码并观察设计模式在其中的应用&#xff0c;…

springboot实现用户统一认证、管理-前端实现

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂 前言 现在是&#xff1a;2022年6月2日15:43:51 上篇文章讲述了springboot中实现用户统一认证的具体内容&#xff0c;主要从后端角度出发的&#xff0c;其实大部分功能还是前端与后端交互的…

Unity3damp;amp;C#分布式游戏服务器ET框架介绍-组件式设计

前几天写了《开源分享 Unity3d客户端与C#分布式服务端游戏框架》&#xff0c;受到很多人关注&#xff0c;QQ群几天就加了80多个人。开源这个框架的主要目的也是分享自己设计ET的一些想法&#xff0c;所以我准备写一系列的文章&#xff0c;介绍下自己的思路跟设计&#xff0c;每…

springboot+vue实现用户统一认证、管理-前端实现

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂”前言现在是&#xff1a;2022年6月2日15:43:51上篇文章讲述了springboot中实现用户统一认证的具体内容&#xff0c;主要从后端角度出发的&#xff0c;其实大部分功能还是前端与后端交互的…

JS中 [] == ![]结果为true,而 {} == !{}却为false, 追根刨底

转载自 JS中 [] ![]结果为true&#xff0c;而 {} !{}却为false&#xff0c; 追根刨底 console.log( [] ![] ) // true console.log( {} !{} ) // false 在比较字符串、数值和布尔值的相等性时&#xff0c;问题还比较简单。但在涉及到对象的比较时&#xff0c;问题就变…

Centos7 amp;amp; Docker amp;amp; Jenkins amp;amp; ASP.NET Core

写在前面 Docker一直很火热&#xff0c;一直想把原本的Jenkins自动部署工具搬到Docker上面&#xff0c;无奈今年一直忙于各种事情&#xff0c;迟迟未实施这个事情&#xff0c;正好迎来了dotnet core 2.0 的正式发布&#xff0c;升级项目的同时&#xff0c;顺便直接将Jenkins搬到…

国民体质测定标准手册及标准解析成JSON文件计算分数,java解析excel文件

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂 前言 现在是&#xff1a;2022年6月14日10:07:27 最近在做体质测评的功能&#xff0c;需要依据《国民体质测定标准手册及标准》&#xff0c;根据用户的个人信息&#xff0c;从而计算出各个…

getchar与putchar用法

#include<stdio.h>main(){int i;igetchar();//相当于char i;scanf("%c",&i); putchar(i);//相当于printf("%c",i); 需要i是字符才能输出不能是变量printf("\n");printf("%d",i);}输出结果一致 #include<stdio.h>main…

TCP为什么是三次握手和四次挥手

转载自 TCP为什么是三次握手和四次挥手 为什么建立连接是三次握手断开连接是四次挥手&#xff1f; 三次握手的流程和四次挥手的流程是什么&#xff1f; 三次握手与四次回收分别对应TCP连接与断开过程 tcp报文格式 标志位含义 ACK&#xff1a;确认序号有效。 SYN&#x…

HTM文件中使用vue

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。 代码啊&#xff0c;尤其是比较重要客户的项目&#xff0c;即使包出去了&#xff0c;代码也一定要回到自己手里&#xff0c;不然干着急。 这个项目&#xff0c;已经经过两手了&#xff0c…

LVS三种模式的区别及负载均衡算法

转载自 LVS三种模式的区别及负载均衡算法 LVS简介 LVS&#xff08;Linux Virtual Server&#xff09;即Linux虚拟服务器&#xff0c;是一个虚拟的服务器集群系统&#xff0c;由章文嵩博士在1998年5月成立&#xff0c;在linux2.6后将lvs自动加入了kernel模块&#xff0c;我们…

王者荣耀是怎样炼成的(一)《王者荣耀》用什么开发,游戏入门,unity3D介绍

在国内&#xff0c;如果你没有听说过《王者荣耀》&#xff0c;那你一定是古董级的人物了。 《王者荣耀》&#xff08;以下简称“农药”&#xff09;&#xff0c;专注于移动端&#xff08;Android、IOS&#xff09;的MOBA游戏。笔者看到这么火爆&#xff0c;就萌生了了解一下这类…

新工作感悟~辞旧迎新~

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂”现在是&#xff1a;2022年6月21日22:33:34公众号又好久没有更新啦。从以前的日更&#xff0c;到后来的周更&#xff0c;再到后来的月更……不知道会不会到不更的结局。。。最近换工作了&…

关于Spring底层原理面试的那些问题,你是不是真的懂Spring?

转载自 关于Spring底层原理面试的那些问题&#xff0c;你是不是真的懂Spring&#xff1f; 1.什么是 Spring 框架&#xff1f;Spring 框架有哪些主要模块&#xff1f; Spring 框架是一个为 Java 应用程序的开发提供了综合、广泛的基础性支持的 Java 平台。Spring帮助开发者解…

ASP.NET Core Web服务器 Kestrel和Http.sys 特性详解

1.1. 名词解释 内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序。 用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取。 1.2. Kestrel基本工作原理 Kestrel是…

Failed to execute

今天用dev c无论打编译什么都是出现如下结果&#xff1a; 后来终于找到解决办法了: 原来是这里出现问题了&#xff0c;我的电脑是32位的&#xff0c;必须也是32位的编译系统。否则不管输入什么都是上面的结果&#xff1b; 所以以后不管下载软件还是编译东西第一步一定要看自…

asp.net core 2.0 web api基于JWT自定义策略授权

JWT(json web token)是一种基于json的身份验证机制&#xff0c;流程如下&#xff1a; 通过登录&#xff0c;来获取Token&#xff0c;再在之后每次请求的Header中追加Authorization为Token的凭据&#xff0c;服务端验证通过即可能获取想要访问的资源。关于JWT的技术&#xff0c;…

BATJ面试必会|Jvm 虚拟机篇

转载自 BATJ面试必会|Jvm 虚拟机篇 目录 一、运行时数据区域 程序计数器 Java 虚拟机栈 本地方法栈 堆 方法区 运行时常量池 直接内存 二、垃圾收集 判断一个对象是否可被回收 引用类型 垃圾收集算法 垃圾收集器 三、内存分配与回收策略 Minor GC 和 Full GC 内存…

让网页背景颜色改变

如何改变背景的颜色呢&#xff0c;这里提供一个方法 <!DOCTYPE html> <html><head><style type"text/css">body {background-color: red}p {margin-left: 1px}</style><title>我yi癫狂</title></head><body>…