在第1章项目结构分析中,我们提到Startup.cs
作为整个程序的入口点,等同于传统的Global.asax
文件,即:用于初始化系统级的信息(例如,MVC中的路由配置)。本章我们就来一一分析,在这里如何初始化这些系统级的信息。
新旧版本之间的Pipeline区别
ASP.NET 5和之前版本的最大区别是对HTTP Pipeline的全新重写,在之前的版本中,请求过滤器的通常是以HttpModule
为模块组件,这些组件针对HttpApplication
里定义的各个周期内的事件进行响应,从而用于实现认证、全局错误处理、日志等功能。传统的Form表单认证就是一个HTTPModule
。HTTPModule
不仅能够过滤Request
请求,还可以和Response
响应进行交互并修改。这些HTTPModule组件都继承于IHttpModule接口,而该接口是位于System.Web.dll
中。
HttpModule代码不仅可以在Global.asax中的各事件周期中进行添加,还可以单独编译成类库并在web.config中进行注册。
新版的ASP.NET 5抛弃了重量级的System.Web.dll,相应地引入了Middleware
的概念,Middleware
的官方定义如下:
Pass through components that form a pipeline between a server and application to inspect, route, or modify request and response messages for a specific purpose.
在服务器和应用程序之间的管线Pipeline之间,针对特定的目的,穿插多个Middleware组件,从而对request请求和response响应进行检
查、路由、或修改。
该定义和传统的HttpModule
以及HttpHandler
特别像。
Middleware的注册和配置
在ASP.NET5中,request请求管线(Pipeline)的访问是在Startup类中进行的,该类时一个约定类,并且里面的ConfigureServices
方法、Configure
方法、以及相应的参数也是事先约定的,所以不能进行改动。
Middleware中的依赖处理:ConfigureServices方法
在ASP.NET5中的各种默认的Middleware中,都使用了依赖注入的功能,所以在使用Middleware中的功能时,需要提前将依赖注入所需要的类型及映射关系都注册到依赖注入管理系统中,即IServiceCollection集合,而ConfigureServices方法接收的就一个IServiceCollection类型的参数,该参数就是所有注册过类型的集合,通过原生的依赖注入组件进行管理(关于ASP.NET5中的依赖注入,我们会在单独章节中进行讲解),在该方法内,我们可以向该集合中添加新的类型和类型映射关系,示例如下:
// Add MVC services to the services container. services.AddMvc();
示例中的代码用于向系统添加Mvc模块相关的Service类型以支撑MVC功能,该方法是一个扩展方法,用于在集合中添加与MVC相关的多个类型。
Middleware的注册和配置:Configure方法
Configure方法的签名如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) { // ... }
Configure
方法接收了三个参数:IApplicationBuilder
类型的参数用于构建整个应用程序的配置信息,IHostingEnvironment
类的env
参数用于访问系统环境变量相关的内容,ILoggerFactory
类型的loggerfactory
用于日志相关的内容处理,其中IApplicationBuilder
类型的参数最为重要,该参数实例app上有一系列的扩展方法用于将各种Middleware注册到request请求管线(Pipeline)中。这种方式和之前ASP.NET中的HTTP管线的主要区别是:新版本中的组合模型替换了旧版本中的事件模型。这也就要求,在新版ASP.NET中,Middleware组件注册的顺序是非常重要的,因为后一个组件可能要使用到前一个组件,所以必须按照依赖的先后顺序进行注册,举例如下,当前MVC项目的模板代码示例如下:
// Add static files to the request pipeline. app.UseStaticFiles(); // Add cookie-based authentication to the request pipeline. app.UseIdentity(); // Add MVC to the request pipeline. app.UseMvc(routes =>{ /*...*/});
示例中的UseStaticFiles
、UseIdentity
、UseMvc
都是IApplicationBuilder
上的扩展方法,在扩展方法中,都会通过调用扩展方法app.UseMiddleware
方法,最终再调用app.Use
方法来注册新的Middleware,该方法定义如下:
public interface IApplicationBuilder { //... IApplicationBuilder Use(Funcmiddleware); }
通过代码,可以看出,middleware是Func
的一个实例,该Func接收一个RequestDelegate
的参数,并返回一个RequestDelegate
类型的值。RequestDelegate
的源码如下:
public delegate Task RequestDelegate(HttpContext context);
通过源码,我们可以看出,RequestDelegate
是一个委托函数,其接收HttpContext
类型的实例,并返回一个Task
类型的异步对象。也就是说RequestDelegate
是一个可以返回自身RequestDelegate
类型函数的函数,整个ASP.NET也就是利用这种方式构建了管线(Pipelien)的组成,在这里,每个middleware都链式到下一个middleware上,并在整个过程中可以对HttpConext
对象进行修改或维护,当然,HttpContext
中就包括了我们常操作的HttpRequest
和HttpResponse
实例对象。
注意:HttpContext
、HttpRequest
、HttpResponse
在ASP.NET 5中都是重新定义的新类型。
Middleware的定义
既然每个middleare都是Func
的一个实例,那是不是Middleware的定义要满足一个规则?是继承于一个抽象基类还是借口?通过翻查相关的代码,我们看到,Middleware是基于约定的形式来定义的,具体约定规则如下:
构造函数的第一个参数必须是处理管线中的下一个处理函数,即RequestDelegate;必须有一个 Invoke 函数, 并接受上下文参数(即HttpContent), 然后返回 Task;
示例如下:
public class MiddlewareName { RequestDelegate _next; public MiddlewareName(RequestDelegate next) { _next = next;// 接收传入的RequestDelegate实例 } public async Task Invoke(HttpContext context) { // 处理代码,如处理context.Request中的内容 Console.WriteLine("Middleware开始处理"); await _next(context); Console.WriteLine("Middleware结束处理"); // 处理代码,如处理context.Response中的内容 } }
通过该模板代码可以看到,首先一个Middleware的构造函数要接收一个RequestDelegate的实例,先保存在一个私有变量里,然后通过调用Invoke
方法(并接收HttpContent
实例)并返回一个Task
,并且在调用Invoke
的方法中,要通过await _next(context);
语句,链式到下一个Middleware上,我们的处理代码主要就是在链式语句的前后执行相关的代码。
举个例子,如果我们要想记录页面的执行时间,首先,我们先定义一个TimeRecorderMiddleware,代码如下:
public class TimeRecorderMiddleware { RequestDelegate _next; public TimeRecorderMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { var sw = new Stopwatch(); sw.Start(); await _next(context); var newDiv = @"页面处理时间:{0} 毫秒