在第1章项目结构分析中,我们提到Startup.cs作为整个程序的入口点,等同于传统的Global.asax文件,即:用于初始化系统级的信息(例如,MVC中的路由配置)。本章我们就来一一分析,在这里如何初始化这些系统级的信息。

新旧版本之间的Pipeline区别

ASP.NET 5和之前版本的最大区别是对HTTP Pipeline的全新重写,在之前的版本中,请求过滤器的通常是以HttpModule为模块组件,这些组件针对HttpApplication里定义的各个周期内的事件进行响应,从而用于实现认证、全局错误处理、日志等功能。传统的Form表单认证就是一个HTTPModuleHTTPModule不仅能够过滤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中的依赖注入,我们会在单独章节中进行讲解),在该方法内,我们可以向该集合中添加新的类型和类型映射关系,示例如下:

  1. // Add MVC services to the services container.
  2. services.AddMvc();

示例中的代码用于向系统添加Mvc模块相关的Service类型以支撑MVC功能,该方法是一个扩展方法,用于在集合中添加与MVC相关的多个类型。

Middleware的注册和配置:Configure方法

Configure方法的签名如下:

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
  2. {
  3. // ...
  4. }

Configure方法接收了三个参数:IApplicationBuilder类型的参数用于构建整个应用程序的配置信息,IHostingEnvironment类的env参数用于访问系统环境变量相关的内容,ILoggerFactory类型的loggerfactory用于日志相关的内容处理,其中IApplicationBuilder类型的参数最为重要,该参数实例app上有一系列的扩展方法用于将各种Middleware注册到request请求管线(Pipeline)中。这种方式和之前ASP.NET中的HTTP管线的主要区别是:新版本中的组合模型替换了旧版本中的事件模型。这也就要求,在新版ASP.NET中,Middleware组件注册的顺序是非常重要的,因为后一个组件可能要使用到前一个组件,所以必须按照依赖的先后顺序进行注册,举例如下,当前MVC项目的模板代码示例如下:

  1. // Add static files to the request pipeline.
  2. app.UseStaticFiles();
  3. // Add cookie-based authentication to the request pipeline.
  4. app.UseIdentity();
  5. // Add MVC to the request pipeline.
  6. app.UseMvc(routes =>{ /*...*/});

示例中的UseStaticFilesUseIdentityUseMvc都是IApplicationBuilder上的扩展方法,在扩展方法中,都会通过调用扩展方法app.UseMiddleware方法,最终再调用app.Use方法来注册新的Middleware,该方法定义如下:

  1. public interface IApplicationBuilder
  2. {
  3. //...
  4. IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
  5. }

通过代码,可以看出,middleware是Func<RequestDelegate, RequestDelegate>的一个实例,该Func接收一个RequestDelegate的参数,并返回一个RequestDelegate类型的值。RequestDelegate的源码如下:

  1. public delegate Task RequestDelegate(HttpContext context);

通过源码,我们可以看出,RequestDelegate是一个委托函数,其接收HttpContext类型的实例,并返回一个Task类型的异步对象。也就是说RequestDelegate是一个可以返回自身RequestDelegate类型函数的函数,整个ASP.NET也就是利用这种方式构建了管线(Pipelien)的组成,在这里,每个middleware都链式到下一个middleware上,并在整个过程中可以对HttpConext对象进行修改或维护,当然,HttpContext中就包括了我们常操作的HttpRequestHttpResponse实例对象。

注意:HttpContextHttpRequestHttpResponse在ASP.NET 5中都是重新定义的新类型。

Middleware的定义

既然每个middleare都是Func<RequestDelegate, RequestDelegate>的一个实例,那是不是Middleware的定义要满足一个规则?是继承于一个抽象基类还是借口?通过翻查相关的代码,我们看到,Middleware是基于约定的形式来定义的,具体约定规则如下:

  1. 构造函数的第一个参数必须是处理管线中的下一个处理函数,即RequestDelegate;
  2. 必须有一个 Invoke 函数, 并接受上下文参数(即HttpContent), 然后返回 Task;

示例如下:

  1. public class MiddlewareName
  2. {
  3. RequestDelegate _next;
  4. public MiddlewareName(RequestDelegate next)
  5. {
  6. _next = next;// 接收传入的RequestDelegate实例
  7. }
  8. public async Task Invoke(HttpContext context)
  9. {
  10. // 处理代码,如处理context.Request中的内容
  11. Console.WriteLine("Middleware开始处理");
  12. await _next(context);
  13. Console.WriteLine("Middleware结束处理");
  14. // 处理代码,如处理context.Response中的内容
  15. }
  16. }

通过该模板代码可以看到,首先一个Middleware的构造函数要接收一个RequestDelegate的实例,先保存在一个私有变量里,然后通过调用Invoke方法(并接收HttpContent实例)并返回一个Task,并且在调用Invoke的方法中,要通过await _next(context);语句,链式到下一个Middleware上,我们的处理代码主要就是在链式语句的前后执行相关的代码。

举个例子,如果我们要想记录页面的执行时间,首先,我们先定义一个TimeRecorderMiddleware,代码如下:

  1. public class TimeRecorderMiddleware
  2. {
  3. RequestDelegate _next;
  4. public TimeRecorderMiddleware(RequestDelegate next)
  5. {
  6. _next = next;
  7. }
  8. public async Task Invoke(HttpContext context)
  9. {
  10. var sw = new Stopwatch();
  11. sw.Start();
  12. await _next(context);
  13. var newDiv = @"<div id=""process"">页面处理时间:{0} 毫秒</div></body>";
  14. var text = string.Format(newDiv, sw.ElapsedMilliseconds);
  15. await context.Response.WriteAsync(text);
  16. }
  17. }

Middleware的注册有很多种方式,如下是实例型注册代码:

  1. app.Use(next => new TimeRecorderMiddleware(next).Invoke);

或者,你也可以使用UseMiddleware扩展方法进行注册,示例如下:

  1. app.UseMiddleware<TimeRecorderMiddleware>();
  2. //app.UseMiddleware(typeof(TimeRecorderMiddleware)); 两种方式都可以

当然,你也可以定义一个自己的扩展方法用于注册该Middleware,代码如下:

  1. public static IApplicationBuilder UseTimeRecorderMiddleware(this IApplicationBuilder app)
  2. {
  3. return app.UseMiddleware<TimeRecorderMiddleware>();
  4. }

最后在Startup类的Configure方法内进行注册,代码如下:

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
  2. {
  3. app.UseTimeRecorderMiddleware(); // 要放在前面,以便进行统计,如果放在Mvc后面的话,就统计不了时间了。
  4. // 等等
  5. }

编译,重启,并访问页面,在页面的底部即可看到页面的运行时间提示内容。

常用Middleware功能的使用

__app.UseErrorPage() __

在IHostingEnvironment.EnvironmentName为Development的情况下,才显示错误信息,并且错误信息的显示种类,可以通过额外的ErrorPageOptions参数来设定,可以设置全部显示,也可以设置只显示Cookies、Environment、ExceptionDetails、Headers、Query、SourceCode SourceCodeLineCount中的一种或多种。

__app.UseErrorHandler("/Home/Error") __

捕获所有的程序异常错误,并将请求跳转至指定的页面,以达到友好提示的目的。

__app.UseStaticFiles() __

开启静态文件也能走该Pipeline管线处理流程的功能。

__app.UseIdentity() __

开启以cookie为基础的ASP.NET identity认证功能,以支持Pipeline请求处理。

直接使用委托定义Middleware的功能

由于Middleware是Func<RequestDelegate, RequestDelegate>委托类型的实例,所以我们也可以不必定义一个单独的类,在Startup类里,使用委托调用的方式就可以了,示例如下:

  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
  2. {
  3. app.Use(new Func<RequestDelegate, RequestDelegate>(next => content => Invoke(next, content)));
  4. // 其它
  5. }
  6. // 注意Invoke方法的参数
  7. private async Task Invoke(RequestDelegate next, HttpContext content)
  8. {
  9. Console.WriteLine("初始化组件开始");
  10. await next.Invoke(content);
  11. Console.WriteLine("管道下步执行完毕");
  12. }

做个简便的Middleware基类

虽然有约定方法,但有时候我们在开发的时候往往会犯迷糊,想不起来到底是什么样的约定,所以,在这里我们可以定义一个抽象基类,然后以后所有的Middleware在定义的时候都继承该抽象类并重载Invoke方法即可,从而可以避免约定忘记的问题。代码如下:

  1. /// <summary>
  2. /// 抽象基类
  3. /// </summary>
  4. public abstract class AbstractMiddleware
  5. {
  6. protected RequestDelegate Next { get; set; }
  7. protected AbstractMiddleware(RequestDelegate next)
  8. {
  9. this.Next = next;
  10. }
  11. public abstract Task Invoke(HttpContext context);
  12. }
  13. /// <summary>
  14. /// 示例Middleware
  15. /// </summary>
  16. public class DemoMiddleware : AbstractMiddleware
  17. {
  18. public DemoMiddleware(RequestDelegate next) : base(next)
  19. {
  20. }
  21. public async override Task Invoke(HttpContext context)
  22. {
  23. Console.WriteLine("DemoMiddleware Start.");
  24. await Next.Invoke(context);
  25. Console.WriteLine("DemoMiddleware End.");
  26. }
  27. }

使用方法和上面的一样。

终止链式调用或阻止所有的Middleware

在有些情况下,当然根据某些条件判断以后,可能不在需要继续往下执行下去了,而是想知己诶返回结果,那么你可以在你的Middleware里忽略对await next.Invoke(content);的调用,直接使用·Response.WriteAsync·方法输出内容。

另外,在有些情况下,你可能需要实现类似之前版本中的handler的功能,即不经常任何Pipeline直接对Response进行响应,新版ASP.NET里提供了一个run方法用于实现该功能,只需要在Configure方法里调用如下代码即可实现类似的内容输出。

  1. app.Run(async context =>
  2. {
  3. context.Response.ContentType = "text/html";
  4. await context.Response.WriteAsync("Hello World!");
  5. });

关于ASP.NET 5 Runtime的内容,请访问:https://msdn.microsoft.com/en-us/magazine/dn913182.aspx

遗留问题

在Mvc项目中,所有的依赖注入类型都是通过IServiceProvider实例来获取的,目前可以通过以下形式获取该实例:

  1. var services = Context.RequestServices; // Controller中
  2. var services = app.ApplicationServices; // Startup中

获取了该实例以后,即可通过如下方法来获取某个类型的对象:

  1. var controller = (AccountController)services.GetService(typeof(AccountController));
  2. // 要判断获取到的对象是否为null

如果你引用了Microsoft.Framework.DependencyInjection命名空间的话,还可以使用如下三种扩展方法:

  1. var controller2 = (AccountController)services.GetService<AccountController>();
  2. // 要判断获取到的对象是否为null
  3. //如下两种方式,如果获取到的AccountController实例为null的话,就会字段抛异常,而不是返回null
  4. var controller3 = (AccountController)services.GetRequiredService(typeof(AccountController));
  5. var controller4 = (AccountController)services.GetRequiredService<AccountController>();

那么问题来了?如何不在Startup和Controller里就可以获取到HttpContext和IApplicationBuilder实例以便使用这些依赖注入服务?

  1. 如何获取IApplicationBuilder实例?

    答案:在Startup里将IApplicationBuilder实例保存在一个单例中的变量上,后期全站就可以使用了。

  2. 如何获取HttpContext实例?

    答案:参考依赖注入章节的普通类的依赖注入

引用:http://www.mikesdotnetting.com/article/269/asp-net-5-middleware-or-where-has-my-httpmodule-gone

同步与推荐

本文已同步至目录索引:解读ASP.NET 5 & MVC6系列

解读ASP.NET 5 & MVC6系列(6):Middleware详解的更多相关文章

  1. 解读ASP.NET 5 & MVC6系列(11):Routing路由

    新版Routing功能介绍 在ASP.NET 5和MVC6中,Routing功能被全部重写了,虽然用法有些类似,但和之前的Routing原理完全不太一样了,该Routing框架不仅可以支持MVC和We ...

  2. 解读ASP.NET 5 & MVC6系列(8):Session与Caching

    在之前的版本中,Session存在于System.Web中,新版ASP.NET 5中由于不在依赖于System.Web.dll库了,所以相应的,Session也就成了ASP.NET 5中一个可配置的模 ...

  3. 解读ASP.NET 5 & MVC6系列

    本系列的大部分内容来自于微软源码的阅读和网络,大部分测试代码都是基于VS RC版本进行测试的. 解读ASP.NET 5 & MVC6系列(1):ASP.NET 5简介 解读ASP.NET 5 ...

  4. 解读ASP.NET 5 & MVC6系列(2):初识项目

    初识项目 打开VS2015,创建Web项目,选择ASP.NET Web Application,在弹出的窗口里选择ASP.NET 5 Website模板创建项目,图示如下: 我们可以看到,此时Web ...

  5. 解读ASP.NET 5 & MVC6系列(7):依赖注入

    在前面的章节(Middleware章节)中,我们提到了依赖注入功能(Dependency Injection),ASP.NET 5正式将依赖注入进行了全功能的实现,以便开发人员能够开发更具弹性的组件程 ...

  6. [转]解读ASP.NET 5 & MVC6系列(7):依赖注入

    本文转自:http://www.cnblogs.com/TomXu/p/4496440.html 在前面的章节(Middleware章节)中,我们提到了依赖注入功能(Dependency Inject ...

  7. 解读ASP.NET 5 & MVC6 ---- 系列文章

    本系列的大部分内容来自于微软源码的阅读和网络,大部分测试代码都是基于VS RC版本进行测试的. 解读ASP.NET 5 & MVC6系列(1):ASP.NET 5简介 解读ASP.NET 5 ...

  8. [转]解读ASP.NET 5 & MVC6系列(8):Session与Caching

    本文转自:http://www.cnblogs.com/TomXu/p/4496445.html 在之前的版本中,Session存在于System.Web中,新版ASP.NET 5中由于不在依赖于Sy ...

  9. 解读ASP.NET 5 & MVC6系列(17):MVC中的其他新特性

    (GlobalImport全局导入功能) 默认新建立的MVC程序中,在Views目录下,新增加了一个_GlobalImport.cshtml文件和_ViewStart.cshtml平级,该文件的功能类 ...

随机推荐

  1. mysql 慢查询的小结

    MySQL优化的第一步应该做的就是排查问题,找出瓶颈,而通常情况下的瓶颈和问题都需要通过观察MySQL的运行情况来进行分析,而对于大多数的程序员来说,最容易发现并解决的问题就是MySQL的慢查询或者没 ...

  2. My year of 2016

    2016, year of excellence.   Year of happiness. In Beijing we can also find some happiness which is s ...

  3. SSH ProxyCommand

    简单的说就是SSH层面的代理服务器,实现通过跳板机执行SSH相关命令到目标机器的代理服务.

  4. HDU 5795 A Simple Nim 打表求SG函数的规律

    A Simple Nim Problem Description   Two players take turns picking candies from n heaps,the player wh ...

  5. 21分钟 MySQL 入门教程(转载!!!)

    21分钟 MySQL 入门教程 目录 一.MySQL的相关概念介绍 二.Windows下MySQL的配置 配置步骤 MySQL服务的启动.停止与卸载 三.MySQL脚本的基本组成 四.MySQL中的数 ...

  6. [leetcode] 题型整理之cycle

    找到环的起点. 一快一慢相遇初,从头再走再相逢.

  7. PDA固定资产条码管理系统软件-解决固定资产实物清查的瓶颈问题,大大提高清查效率

    固定资产管理系统是企业信息化管理中的一个重要组成部分,固定资产具有价值高,使用周期长.使用地点分散.管理难度大等特点.一个企业的良性发展,避免不了的要涉及到企业资产的有效管理.对于那些技术装备密集型的 ...

  8. selenium使用Xpath定位之完整篇

    其中有一片文章提到了xpath元素定位,但是该文章中有些并不能适应一些特殊与个性化的场景.在文本中提供xpath元素的定位终极篇,你一定能在这里找到你需要的解决办法. 第一种方法: 通过绝对路径做定位 ...

  9. CSS3动画制作

    CSS3动画制作 rotate 绕中心旋转 fadeInPendingFadeOutUp 先渐现,停留2s,再向上滑动并逐渐消失 fadeInUp2D 向上滑动并渐现, 因Animate.css的fa ...

  10. 使用MonkeyTest对Android客户端进展压力测试

    Monkey是Android中的一个命令行工具,可以运行在模拟器里或实际设备中.它向系统发送伪随机的用户事件流(如按键输入.触摸屏输入.手势输入等),实现对正在开发的应用程序进行压力测试. 先来看一条 ...