4、HttpContext 第一个对象
我们的ASP.NET Core Mini由7个核心对象构建而成。第一个就是大家非常熟悉的HttpContext对象,它可以说是ASP.NET Core应用开发中使用频率最高的对象。要说明HttpContext的本质,还得从请求处理管道的层面来讲。对于由一个服务器和多个中间件构建的管道来说,面向传输层的服务器负责请求的监听、接收和最终的响应,当它接收到客户端发送的请求后,需要将它分发给后续中间件进行处理。对于某个中间件来说,当我们完成了自身的请求处理任务之后,在大部分情况下也需要将请求分发给后续的中间件。请求在服务器与中间件之间,以及在中间件之间的分发是通过共享上下文的方式实现的。
如上图所示,当服务器接收到请求之后,会创建一个通过HttpContext表示的上下文对象,所有中间件都是在这个上下文中处理请求的,那么一个HttpContext对象究竟携带怎样的上下文信息呢?我们知道一个HTTP事务(Transaction)具有非常清晰的界定,即接收请求、发送响应,所以请求和响应是两个基本的要素,也是HttpContext承载的最核心的上下文信息。
我们可以将请求理解为输入、响应理解为输出,所以应用程序可以利用HttpContext得到当前请求所有的输入信息,也可以利用它完成我们所需的所有输出工作。为此我们为ASP.NET Core Mini定义了如下这个极简版本的HttpContext。
public class HttpContext { public HttpRequest Request { get; } public HttpResponse Response { get; } } public class HttpRequest { public Uri Url { get; } public NameValueCollection Headers { get; } public Stream Body { get; } } public class HttpResponse { public NameValueCollection Headers { get; } public Stream Body { get; } public int StatusCode { get; set;} }
如上面的代码片段所示,HttpContext通过它的两个属性Request和Response来表示请求和响应,它们对应的类型分别为HttpRequest和HttpResponse。通过前者,我们可以得到请求的地址、手部集合和主体内容,利用后者,我们可以设置响应状态码,也可以设置首部和主体内容。
5、RequestDelegate 第二个对象
RequestDelegate是我们介绍的第二个核心对象。我们从命名可以看出这是一个委托(Delegate)对象,和上面介绍的HttpContext一样,我们也只有从管道的角度才能充分理解这个委托对象的本质。
软件的架构设计有这样的认识:好的设计一定是“简单”的设计。所以每当我在设计某个开发框架的时候,一直会不断告诉我自己:“还能再简单点吗?”。我们上面介绍的ASP.NET Core管道的设计就具有“简单”的特质:Pipeline = Server + Middlewares。但是“还能再简单点吗?”,其实是可以的:我们可以将多个Middleware构建成一个单一的“HttpHandler”,那么整个ASP.NET Core框架将具有更加简单的表达:Pipeline =Server + HttpHandler。
那么我们如来表达HttpHandler呢?我们可以这样想:既然针对当前请求的所有输入和输出都通过HttpContext来表示,那么HttpHandler就可以表示成一个Action<HttpContext>对象。那么HttpHandler在ASP.NET Core中是通过Action<HttpContext>来表示的吗?其实不是的,原因很简单:Action<HttpContext>只能表示针对请求的 “同步” 处理操作(void),但是针对HTTP请求既可以是同步的,也可以是异步的,更多地其实是异步的。
那么在.NET Core的世界中如何来表示一个同步或者异步操作呢?你应该想得到,那就是Task对象,那么HttpHandler自然就可以表示为一个Func<HttpContext,Task>对象。由于这个委托对象实在太重要了,所以我们将它定义成一个独立的类型。
public delegate Task RequestDelegate(HttpContext context);
其实跟Func<HttpContext,Task>是一样的
6、Middleware 第三个对象
在对RequestDelegate这个委托对象具有充分认识之后,我们来聊聊中间件又如何表达,这也是我们介绍的第三个核心对象。中间件在ASP.NET Core被表示成一个Func<RequestDelegate, RequestDelegate>对象,也就是说它的输入和输出都是一个RequestDelegate。
对于为什么会采用一个Func<RequestDelegate, RequestDelegate>对象来表示中间件,很多初学者会很难理解。我们可以这样的考虑:对于管道的中的某一个中间件来说,由后续中间件组成的管道体现为一个RequestDelegate对象,由于当前中间件在完成了自身的请求处理任务之后,往往需要将请求分发给后续中间件进行处理,所以它需要将由后续中间件构成的RequestDelegate作为输入。
当代表中间件的委托对象执行之后,我们希望的是将当前中间件“纳入”这个管道,那么新的管道体现的RequestDelegate自然成为了输出结果。所以中间件自然就表示成输入和输出均为RequestDelegate的Func<RequestDelegate, RequestDelegate>对象。
IapplicationBuilder接口中定义Use方法
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
7、ApplicationBuilder 第四个对象
ApplicationBuilder是我们认识的第四个核心对象。从命名来看,这是我们接触到的第二个Builder,既然它被命名为ApplicationBuilder,意味着由它构建的就是一个Application。那么在ASP.NET Core框架的语义下应用(Application)又具有怎样的表达呢?
对于这个问题,我们可以这样来理解:既然Pipeline = Server + HttpHandler,那么用来处理请求的HttpHandler不就承载了当前应用的所有职责吗?那么HttpHandler就等于Application,由于HttpHandler通过RequestDelegate表示,那么由ApplicationBuilder构建的Application就是一个RequestDelegate对象。
由于表示HttpHandler的RequestDelegate是由注册的中间件来构建的,所以ApplicationBuilder还具有注册中间件的功能。基于ApplicationBuilder具有的这两个基本职责,我们可以将对应的接口定义成如下的形式。Use方法用来注册提供的中间件,Build方法则将注册的中间件构建成一个RequestDelegate对象。
public interface IApplicationBuilder { IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); RequestDelegate Build(); }
如下所示的是针对该接口的具体实现。我们利用一个列表来保存注册的中间件,所以Use方法只需要将提供的中间件添加到这个列表中即可。当Build方法被调用之后,我们只需按照与注册相反的顺序依次执行表示中间件的Func<RequestDelegate, RequestDelegate>对象就能最终构建出代表HttpHandler的RequestDelegate对象。
public class ApplicationBuilder : IApplicationBuilder { private readonly List<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>(); public RequestDelegate Build() { _middlewares.Reverse(); return httpContext => { RequestDelegate next = _ => { _.Response.StatusCode = 404; return Task.CompletedTask; }; foreach (var middleware in _middlewares) { next = middleware(next); } return next(httpContext); }; } public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _middlewares.Add(middleware); return this; } }
在调用第一个中间件(最后注册)的时候,我们创建了一个RequestDelegate作为输入,后者会将响应状态码设置为404。所以如果ASP.NET Core应用在没有注册任何中间的情况下总是会返回一个404的响应。如果所有的中间件在完成了自身的请求处理任务之后都选择将请求向后分发,同样会返回一个404响应。
Core中的IapplicationBuilder定义
// 摘要:Defines a class that provides the mechanisms to configure an application's requestpipeline. public interface IApplicationBuilder { // Gets or sets the System.IServiceProvider that provides access to the application'sservice container. IServiceProvider ApplicationServices { get; set; } // // 摘要: // Gets the set of HTTP features the application's server provides. IFeatureCollection ServerFeatures { get; } // // 摘要: // Gets a key/value collection that can be used to share data between middleware. IDictionary<string, object> Properties { get; } // // 摘要: // Builds the delegate used by this application to process HTTP requests. // 返回结果: // The request handling delegate. RequestDelegate Build(); // // 摘要: // Creates a new Microsoft.AspNetCore.Builder.IApplicationBuilder that shares the //Microsoft.AspNetCore.Builder.IApplicationBuilder.Properties of this Microsoft.AspNetCore.Builder.IApplicationBuilder. // // 返回结果: // The new Microsoft.AspNetCore.Builder.IApplicationBuilder. IApplicationBuilder New(); // // 摘要: // Adds a middleware delegate to the application's request pipeline. IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware); }