大部分中间件都是要依赖于 HTTP 管道和 HttpAbstractions 提供的上下文的,所以有必要先介绍一下 ASP.NET Core 在管道中封装了什么,了解清楚之后有助于更高的进阶学习。你看不懂也没有关系,对这些概念有一个认识就可以了,不需要你精通每一步都具体做了哪些东西,达到会用就是我的目的。因为我觉得随着你以后开发的深入,对这些东西使用多了之后,自然会想了解更多,到那个时候,你就会去看源码了。
IApplicationBuilder
IApplicationBuilder
是应用大家最熟悉它的地方应该就是位于 Startup.cs 文件中的 Configure 方法了吧
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseMvc();
}
IApplicationBuilder 就像其名字一样,其实很简单,就是对内做了一个接口封装,对外提供了一个扩展,具体来说就是
对内封装了
IServiceProvider ApplicationServices { get; set; }
:应用程序使用的服务,就是ConfigureServices注入的那些。IFeatureCollection ServerFeatures { get; }
: 工具箱对象,下面会提到的。IDictionary<string, object> Properties { get; }
:横跨各个中间件,在他们之间共享数据。
对外扩展了
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
:注册中间件
还有一个 Build 用来返回管道最终的 HttpContext 状态
RequestDelegate Build();
: 最终的 RequestDelegate 形态
没什么复杂的东西,就这几个东西需要了解,下面是一个 IApplicationBuilder
的贴图,看起来应该会更直观。
RequestDelegate
RequestDelegate 是一个委托,是以委托形式对 HttpContext 的封装,在 ASP.NET Core 中也是非常重要的一个对象,先看一下定义吧。
namespace Microsoft.AspNetCore.Http
{
public delegate Task RequestDelegate(HttpContext context);
}
为什么说这个对象很重要呢,因为在管道中所有的中间件都是直接或者间接的使用到它来做一些功能,因为它包装这 HttpContext 的嘛,而在Web开发中 HttpContext 的地位又是如此的高,接下来就说说 HttpContext
。
HttpContext
HttpContext 应该是做Web开发最熟悉的一个对象了,在这个对象中,封装着Http整个管道中的关键对象信息,这个对象同时伴随着整个管道中传输,也就是会经历整个请求的生命周期,所以这个对象非常重要,也是学习Web开发的一个基础知识。
与传统 .NET Framework 中的 System.Web.HttpContext
不同的是,在 ASP.NET Core 中 HttpContext 是一个抽象类,位于 Microsoft.AspNetCore.Http
命名空间下。它有一个默认的实现叫 DefaultHttpContext
位于 Microsoft.AspNetCore.Http 程序集。
我们知道以前的 System.Web.HttpContext 是怎么创建的吧?
在传统ASP.NET程序中,IIS接收到请求之后,会交给 aspnet_isapi.dll 这个程序集,程序集启动 ASP.NET 运行环境,然后调用 ISAPIRuntime 进行封装,封装之后就是一个 HttpWorkRequest 对象了,然后由 HttpRuntime将其转化为一个HttpContext。
所以在System.Web.HttpContext中,其构造函数为 HttpWorkerRequest
namespace System.Web
{
public sealed class HttpContext : IServiceProvider, IPrincipalContainer
{
public HttpContext(HttpWorkerRequest wr);
}
}
也就是说其实构建HttpContext所有的信息都是来自于 IIS 传递下来的 HttpWorkerRequest
。
现在,忘了它吧,是的全部忘掉,这个时候你又要骂微软了,尼玛当年为了面试这个东西不知道背了多少遍。 =_=
没办法,时代要发展要进步,我们也需要不断的学习,那在 ASP.NET Core 中,没有了 IIS ,它的 HttpContext 又是如何生成的呢? 构建 HttpContext 所需的信息又怎么来的呢? 客官不要着急,听我慢慢分解
IFeatureCollection
这里就需要先说一下另外一个比较重要接口了,它就是 IFeatureCollection
接口,这个接口是干嘛的呢? 我们先看一下它的定义:
public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>
{
bool IsReadOnly { get; }
int Revision { get; }
object this[Type key] { get; set; }
TFeature Get<TFeature>();
void Set<TFeature>(TFeature instance);
}
看到这个接口的定义应该能够明白其功能了,接口继承于IEnumerable<KeyValuePair<Type, object>>
说明接口是一个键值对的集合,我们给接口取个名字吧,叫“工具箱”。
有些同学应该猜出来了,是的,ASP.NET Core中构建 HttpContext 就是所有需要的组件都是来自于 工具箱,那么 工具箱 里面都有什么呢?可以看到工具箱里面有 IHttpRequestFeature
, IHttpResponseFeature
, ISessionFeature
等等还有很多就不一一列举了,正是由于这一个一个的工具箱里面的工具,最终构建成为了整个 HttpContext 对象。我们来看一下 HttpContext 对象的构造函数:
namespace Microsoft.AspNetCore.Http
{
public class DefaultHttpContext : HttpContext
{
public DefaultHttpContext(IFeatureCollection features)
{
Initialize(features);
}
}
}
没错吧,有同学可能要问了,工具箱里面的这些工具什么时候初始化的呢? 先别急,我们先看看我们熟悉的 Request 和 Response。
HttpRequest
同样在 ASP.NET Core ,HttpRequest 也成为了一个抽象类,它的默认实现叫 DefaultHttpRequest
,主要是封装浏览器对服务器的请求的各种数据,包括浏览器请求的网址,查询字符串数据或表单数据等等…
不一一详细介绍了,都是很简单的内容。
HttpResponse
HttpResponse 也成为了一个抽象类,它的默认实现叫 DefaultHttpResponse
,主要是是服务器接收到浏览器的请求后,处理返回结果常用的一个对象。
ASP.NET Core 管道
是时候该解释一下上面的 工具箱 初始化的问题了,我想我用一个图来直接说明管道中的请求再合适不过了,下面就是一个 ASP.NET Core 整个服务端构建管道一个流程图:
从图中可以看到,RequestDelegate 携带着 HttpContext 一路经过各种 Server、Hosting 等,最终到达了由 IApplicationBuilder 构建出来的 Application pipeline 这一管道区域,然后再经过各种中间件处理,最终构建出来了我们的 Response,而我们的工具箱 也正是在这个过程中变得 “ 饱满 ” 起来。
有一个需要知道的知识点就是,中间件是怎么样添加或者叫注册到管道中的呢? 又是如何被应用起来的呢?
上面的图可以看到,橙色区域的 Application pipeline 是由 IApplicationBuilder 构建起来的。也就是说我们可以在 IApplicationBuilder 做点什么东西来添加我们的中间件。 是的 IApplicationBuilder 暴露出来了一个 IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
方法来让我们注册中间件,也就是说位于 Startup.cs
文件中的 Configure 方法。
那么又是怎么样应用起来的呢? IApplicationBuilder
在 Hosting 中有一个 IApplicationBuilderFactory 的对象,Hosting 通过这个Factory 创建之后就会传递到了 HostingApplication 对象中,最后由 IWebHost 对象调用 IServer 对象的Start 同时把 HostingApplication传递进去来最终启动服务端。可以对照上面的图理解一下。