本文作为学习过程中的一个记录。
学习文章地址:
https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html
一. ASP.NET Core 框架上的 Hello World程序
public class Program
{
public static void Main()
=> new WebHostBuilder()
.UseKestrel()
.Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!")))
.Build()
.Run();
}
WebHost :承载Web应用的宿主;
WebHostBuilder :WebHost的构建者;
而 在WebHostBuilder在调用 Build方法之前,调用的 两个方法:
UseKestrel :旨在注册一个名为Kestrel的服务器
Configure:为了注册一个用来处理请求的中间件
在上面的代码中,中间件在响应的主体内容中写入了一个 Hello World 的文本。
当我们在调用Run方法启动作为应用宿主的 WebHost的时候,WebHost会利用WebHostBuilder提供的服务器和中间件构建一个请求处理管道。
而下面主要讲的就是 这个管道是如何被构建起来的,以及该管道采用怎样的请求处理流程。
二. 在我们自己的ASP.NET Core Mini上面开发的 Hello World
本文作为学习过程中的一个记录。
学习文章地址:
https://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html
一. ASP.NET Core 框架上的 Hello World程序
public class Program
{
public static void Main()
=> new WebHostBuilder()
.UseKestrel()
.Configure(app => app.Run(context => context.Response.WriteAsync("Hello World!")))
.Build()
.Run();
}
WebHost :承载Web应用的宿主;
WebHostBuilder :WebHost的构建者;
而 在WebHostBuilder在调用 Build方法之前,调用的 两个方法:
UseKestrel :旨在注册一个名为Kestrel的服务器
Configure:为了注册一个用来处理请求的中间件
在上面的代码中,中间件在响应的主体内容中写入了一个 Hello World 的文本。
当我们在调用Run方法启动作为应用宿主的 WebHost的时候,WebHost会利用WebHostBuilder提供的服务器和中间件构建一个请求处理管道。
而下面主要讲的就是 这个管道是如何被构建起来的,以及该管道采用怎样的请求处理流程。
二. 在我们自己的ASP.NET Core Mini上面开发的 Hello World
代码说明:
在创建出 WebHostBuilder 之后,我们调用了它的扩展方法 UseHttpListener 注册了一个自定义的基于 HttpListener的服务器;
随后针对 Configure 方法的调用中,我们注册了三个中间件。
由于中间件最终是通过 Delegate对象来体现的,所以我们可以将中间件定义成与Delegate类型具有相同签名的方法。
程序运行后,得到的输出结果:
三. 自定义的ASP.NET Core Mini框架讲解
下面主要是对 ASP.NET Core Mini框架的构建过程中关键部分的讲解。
主要涉及 HttpContext、RequestDelegate、Middleware、ApplicationBuilder、Server、WebHost、WebHostBuilder 等七个对象;
另外 会讲到 HttpContext与Server之间的适配;HttpListenerServer等;
1. 第一个对象:HttpContext
关于 HttpContext的本质,还得从请求处理管道的层面来讲。
对于由一个服务器和多个中间件构建的管道来说,面向传输层的服务器负责请求的监听、接收和最终的响应;
当它接收到客户端发送的请求后,需要将它分发给后续中间件进行处理。
对于某个中间件来说,当我们完成了自身的请求处理任务之后,在大部分情况下,也需要将请求分发给后续的中间件。
请求在服务器与中间件之间,以及在中间件之间的分发是通过共享上下文的方式实现的。
( 如上图,当服务器接收到请求之后,会创建一个通过HttpContext表示的上下文对象,所有中间件都是在这个上下文中处理请求的;
那么一个HttpContext对象究竟携带了怎样的上下文信息呢?
我们知道一个HTTP事务具有非常清晰的界定,即接收请求、发送响应;
所以请求和响应是两个基本的要素,也是HttpContext承载的最核心的 上下文信息。)
故,HttpContext的核心要素:请求和响应
2. 第二个对象:RequestDelegate
这是一个委托,也需要从管道的角度才能充分理解这个委托对象的本质。
2.1 管道的设计
可以总结为 Pipeline = Server + Middlewares ,再精简写的话,可以写为 Pipeline = Server + HttpHandler .
2.2 那么,我们如何来表达HttpHandler呢?
既然针对当前请求的所有输入和输出都通过HttpContext来表示,那么 HttpHandler就可以表示成一个 Action<HttpContext>对象。
那么HttpHandler在ASP.NET Core中时通过 Action<HttpContext>来表示的吗?
其实不是的,原因很简单:Action<HttpContext>只能表示针对请求的 同步的处理操作,但是针对 HTTP 请求既可以是同步的,也可以是异步的,更多的其实是异步的。
那么在 .NET Core的世界中如何来表示一个同步或者异步操作呢?就是Task对象,那么 HttpHandler自然可以表示为一个 Func<HttpContext,Task>对象。
由于这个委托对象实在太重要了,所以我们将它定义成一个独立的类型:delegate Task RequestDelegate(HttpContext context) 。
3. 第三个对象:Middleware
中间件在ASP.NET Core 中被表示成一个 Func<RequestDelegate,RequestDelegate>对象,即它的输入和输出都是一个RequestDelegate。
为什么采用一个Func<RequestDelegate,RequestDelegate>对象来表示中间件。是因为这样的考虑:
对于管道中的某一个中间件来说,由后续中间件组成的管道体现为一个RequestDelegate对象,由于当前中间件在完成了自身的请求处理任务之后,往往需要将请求分发给后续中间件进行处理,所以它需要将由后续中间件构成的RequestDelegate作为输入。
即:上一个中间件的输出需要可以作为下一个中间件的输入,所以设计为Func<RequestDelegate,RequestDelegate>对象
4. 第四个对象:ApplicationBuilder
ApplicationBuilder 是用来构建 Application的。
既然 Pipeline = Server + HttpHandler , 可以看出HttpHandler承载了当前应用的所有职责,那么 HttpHandler就等于 Application。
由于 HttpHandler通过RequestDelegate表示,那么由ApplicationBuilder构建的Application就是一个RequestDelegate对象。(职责1)
由于表示HttpHandler的RequestDelegate是由注册的中间件来构建的,所以ApplicationBuilder还具有注册中间件的功能。(职责2)
基于ApplicationBuilder具有的这两个基本职责,我们可以将对应的接口定义为如下形式。
Use 方法用来注册提供的中间件,Builder方法则将注册的中间件构建成一个RequestDelegate对象。
public interface IApplicationBuilder
{
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
RequestDelegate Build();
}
下面是针对这个接口的具体实现。
我们用一个列表保存注册的中间件,所以Use方法只需要将提供的中间件添加到这个列表中即可。
当Build方法被调用后,我们只需要按照与注册相反的顺序依次执行表示中间件的Func<RequestDelegate,RequestDelegate>对象,就能最终构建出代表HttpHandler的RequestDelegate对象。
在调用第一个中间件(最后注册)的时候,我们创建了一个RequestDelegate作为输入,后者会将响应状态码设置为404。
所以如果ASP.NET Core应用在没有注册任何中间件的情况下,总是返回一个404响应。
如果所有中间件在完成了自身的请求处理任务之后都选择将请求向后分发,同样会返回一个404响应。
总结:对于上面的四个对象,从后向前依次对前一个进行包装。
5. 第五个对象:Server
当我们运行(Run)作为应用宿主的WebHost的时候,服务器它被自动启动。
启动后的服务器会绑定到指定的端口进行请求监听,一旦有请求抵达,服务器会根据该请求创建出代表上下文的HttpContext对象,
并将该上下文作为输入,调用由所有注册中间件构建而成的RequestDelegate对象。
简单起见,我们使用如下简写的IServer接口来表示服务器。
我们通过定义在IServer接口的唯一方法 StartAsync启动服务器,
作为参数的 handler 正是由所有中间件共同构建而成的RequestDelegate对象
public interface IServer
{
Task StartAsync(RequestDelegate handler);
}
6. HttpContext和Server之间的适配
面向应用层的HttpContext对象是对请求和相应的封装,但是请求最初来源于服务器,针对HttpContext的任何响应操作也必须作用于当前的服务器才能真正起作用。
现在问题来了,所有的ASP.NET Core应用使用的都是同一个HttpContext类型,但是却可以注册不同类型的服务器,我们必须解决两者之间的适配问题。
同一个HttpContext类型与不同服务器类型之间的适配可以通过添加一个抽象层来解决,我们定义该层的对象为Feature。
如上图,我们可以定义一系列的Feature接口来为HttpContext提供上下文信息,其中最重要的就是提供请求的 IRequestFeature和完成响应的IResponseFeature接口。
那么具体的服务器只需要实现这些Feature接口就可以了。
下面是一些代码片段。我们定义了一个IFeatureCollection接口来表示存放Feature对象的集合。
为了编程上的便利,我们定义了两个扩展方法 Set<T>和Get<T>来设置和获取Feature对象。
如下,用来提供请求的IHttpRequestFeature和提供响应IHttpResponseFeature接口的定义,可以看出它们具有和HttpRequest和HttpResponse完全一致的成员定义。
接下来,我们来看看HttpContext的具体实现。
ASP.NET Core Mini的HttpContext只包含Request和Response两个属性成员,对应的类型分别为HttpRequest和HttpResponse,下面是这两个类型的具体实现。
其中,HttpRequest和HttpResponse都是通过一个IFeatureCollection对象构建而成的,它们对应的属性成员均由包含在这个Feature集合中的IHttpRequestFeature和IHttpResponseFeature对象来提供。
HttpContext的实现就更加简单了。我们在创建一个HttpContext对象时同样会提供一个IFeatureCollection对象,
我们利用该对象创建对应的HttpRequest和HttpResponse对象,并作为其对应的属性值。
总结:在HttpContext中传入 IFeatureCollection对象,HttpContext中的成员对象HttpRequest和HttpResponse会利用这个IFeatureCollection来被构建。从而完成HttpContext的构建。当然,其中少不了需要Server部分,下面会讲。
7. HttpListenerServer 服务器
在对服务器和它与HttpContext的适配原理有清晰的认识之后,我们尝试着定义一个服务器。
在前面,我们利用WebHostBuilder的扩展方法UseHttpListener注册了一个HttpListenerServer,我们现在来看看这个采用HttpListener作为监听器的服务器类型是如何实现的。
由于所有的服务器都需要有自己的Feature实现来为HttpContext提供对应的上下文信息,所以我们得先来为HttpListenerServer定义相应的接口。
对HttpListener监听器稍微了解的朋友应该知道它在接收到请求之后同时会创建一个自己的上下文对象,对应的类型为HttpListenerContext。
如果采用HttpListenerServer作为应用的服务器,意味着HttpContext承载的上下文信息最初来源于这个HttpListenerContext,所以Feature的目的旨在解决这两个上下文之间的适配问题。
总结:Feature实际上解决的就是HttpContext和HttpListenerContext之间的适配问题。
下面的HttpListenFeature就是我们为HttpListenerServer定义的Feature。HttpListenerFeature同时实现了IHttpRequestFeature和IHttpResponseFeature,实现的 6 个属性最初都来源于创建该对象提供的HttpListenerContext对象。
当HttpListener监听到抵达的请求后,我们会得到一个HttpListenerContext对象,此时我们只需要据此创建一个HttpListenerFeature对象,
并且它分别以IHttpRequestFeature和IHttpResponseFeature接口类型注册到创建FeatureCollection集合上。
我们最终利用这个FeatureCollection对象创建出代表上下文的HttpContext,然后将它作为参数调用由所有中间件公共构建的RequestDelegate对象即可。
8. 第六个对象:WebHost
到目前为止,我们已经知道了由一个服务器和多个中间件构成的管道是如何完整针对请求的监听、接收、处理和最终响应的,接下来讨论这样的管道是如何被构建出来的。
管道是在作为应用宿主的WebHost对象启动的时候被构建出来的,在ASP.NET Core Mini 中,
我们将表示应用宿主的IWebHost接口简写成如下形式:
只包含一个StartAsync方法来启动应用程序。
public interface IWebHost
{
Task StartAsync();
}
由于由WebHost构建的管道由Server和HttpHandler构成,我们在默认实现的WebHost类型中,我们直接提供这两个对象。
在实现的StartAsync方法中,我们只需要将后者作为参数调用前者的StartAsync方法将服务器启
9. 第七个对象:WebHostBuilder
WebHost的作用:就是创建作为应用宿主的WebHost。
由于在创建WebHost的时候,需要提供注册的服务器和由所有注册中间件构建而成的RequestDelegate,
所以在对应接口IWebHostBuilder中,我们为它定义了三个核心方法。
public interface IWebHostBuilder
{
IWebHostBuilder UseServer(IServer server);
IWebHostBuilder Configure(Action<IApplicationBuilder> configure);
IWebHost Build();
}
除了用来创建WebHost的Build方法之外,我们提供了用来注册服务器的UseServer方法和用来注册中间件的Configure方法。
Configure方法提供了一个类型为 Action<IApplicationBuilder>的参数,
意味着我们针对中间件的注册时利用上面介绍的IApplicationBuilder对象来完成的。
如下代码,WebHostBuilder是针对IWebHostBuilder接口的默认实现,
它具有两个字段分别用来保存注册的中间件和调用Configure方法提供的Action<IApplicationBuilder>对象。
当Build方法被调用之后,我们创建一个ApplicationBuilder对象,并将它作为参数调用这些Action<IApplicationBuilder>委托,
进而将所有中间件全部注册到这个ApplicationBuilder对象上。
我们最终调用它的Build方法得到所有中间件共同构建的RequestDelegate对象,并利用它和注册的服务器构建作为应用宿主的WebHost对象。
至此,本篇结束。
补充:
这里补充的是按照这里的学习,实现这个程序的过程。
1. 首先 创建 Feature.cs 文件
里面定义了请求和响应里面需要的一些内容。
2. 创建 FeatureCollection.cs 文件
这个应该就是 用来装 请求和响应 的。
注:1,2两个文件中的接口或者类,不依赖其他自定义类
3. 创建 HttpContext.cs 文件
这个里面定义了 请求实体类,响应实体类,共享上下文(HttpContext), 以及响应的扩展方法输出内容。
4.创建 RequestDelegate.cs 文件
这个用来承载 共享上下文 HttpContext 的。
5. 创建 ApplicationBuilder.cs 文件
这个是用来承载 RequestDelegate 的,并且存放中间件及使之串起来的。
6. 创建 IServer.cs 文件
这个是用来定义服务器接口的。
7.创建 HttpListenerFeature.cs 文件
我们的共享上下文信息最初来源于这个类中的 HttpListenerContext。它是在服务器中,用来接收 HttpListener 中的上下文对象的(即HttpListenerContext)。
这里把 它按 请求和响应的接口定义进行拆分。
8. 创建 HttpListenerServer.cs 文件
这个是服务器文件,其中定义了监听器,监听的url集合,及启动监听,创建共享上下文,及使用RequestDelegate类型的处理器承载 上下文等操作。
9. 创建 WebHost.cs 文件
这里是用来创建宿主的,用来把服务器和中间件服务连接起来的。
10. 创建 WebHostBuilder.cs 文件
这个是宿主构建者,用来设置服务器,配置中间件,以及 Build 出宿主WebHost的。
11. 最后,创建 Program.cs 文件
这其中包括 创建宿主构建者,设置服务器,配置中间件, Build成宿主,及启动宿主等。
代码结束!!
12. 上面的代码实际已经结束了,但是发现编译的时候报错。
C# 7.0 不支持Program.cs中的用法。
怎么修改呢?
右键项目---> 属性----> 生成 ----> 高级 ---->
然后在 常规 下的语言版本中,选择 c#最新次要版本
如下
13. 重新编译,成功
14. 运行,然后在 浏览器中 输入 http://localhost:5000
可以在代码中打上断点,观察执行过程。
这里,把我自己写的也上传到github了,方便自己查阅,有疑问的小伙伴可以自己去原文学习
我的github地址:https://github.com/Vincent-yuan/asp.net_core_mini
原文链接:https://www.cnblogs.com/Vincent-yuan/p/11318718.html
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com