Middleware详解

在第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中的依赖注入,我们会在单独章节中进行讲解),在该方法内,我们可以向该集合中添加新的类型和类型映射关系,示例如下:

// 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 =>{ /*...*/});

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

public interface IApplicationBuilder
{
//...
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}

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

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;

示例如下:

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 = @"<div id=""process"">页面处理时间:{0} 毫秒</div></body>";
var text = string.Format(newDiv, sw.ElapsedMilliseconds);
await context.Response.WriteAsync(text);
}
}

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

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

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

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

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

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

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

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

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

常用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类里,使用委托调用的方式就可以了,示例如下:

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

做个简便的Middleware基类

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

/// <summary>
/// 抽象基类
/// </summary>
public abstract class AbstractMiddleware
{
protected RequestDelegate Next { get; set; }
protected AbstractMiddleware(RequestDelegate next)
{
this.Next = next;
}
public abstract Task Invoke(HttpContext context);
} /// <summary>
/// 示例Middleware
/// </summary>
public class DemoMiddleware : AbstractMiddleware
{
public DemoMiddleware(RequestDelegate next) : base(next)
{
}
public async override Task Invoke(HttpContext context)
{
Console.WriteLine("DemoMiddleware Start.");
await Next.Invoke(context);
Console.WriteLine("DemoMiddleware End.");
}
}

使用方法和上面的一样。

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

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

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

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

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

遗留问题

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

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

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

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

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

var controller2 = (AccountController)services.GetService<AccountController>();
// 要判断获取到的对象是否为null //如下两种方式,如果获取到的AccountController实例为null的话,就会字段抛异常,而不是返回null
var controller3 = (AccountController)services.GetRequiredService(typeof(AccountController));
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系列

Middleware详解的更多相关文章

  1. 解读ASP.NET 5 & MVC6系列(6):Middleware详解

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

  2. laravel框架的中间件middleware的详解

    本篇文章给大家带来的内容是关于laravel框架的中间件middleware的详解,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. laravel中间件是个非常方便的东西,能将一些逻辑 ...

  3. Scrapy框架——介绍、安装、命令行创建,启动、项目目录结构介绍、Spiders文件夹详解(包括去重规则)、Selectors解析页面、Items、pipelines(自定义pipeline)、下载中间件(Downloader Middleware)、爬虫中间件、信号

    一 介绍 Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速.简单.可扩展的方式从网站中提取所需的数据.但目前Scrapy的用途十分广泛,可 ...

  4. [转帖]ASP.NET Core 中间件(Middleware)详解

    ASP.NET Core 中间件(Middleware)详解   本文为官方文档译文,官方文档现已非机器翻译 https://docs.microsoft.com/zh-cn/aspnet/core/ ...

  5. net core 中间件详解及项目实战

    net core 中间件详解及项目实战 前言 在上篇文章主要介绍了DotNetCore项目状况,本篇文章是我们在开发自己的项目中实际使用的,比较贴合实际应用,算是对中间件的一个深入使用了,不是简单的H ...

  6. 详解Grunt插件之LiveReload实现页面自动刷新(两种方案)

    http://www.jb51.net/article/70415.htm    含Grunt系列教程 这篇文章主要通过两种方案详解Grunt插件之LiveReload实现页面自动刷新,需要的朋友可以 ...

  7. Android OS体系结构详解

    Google于2007年11月5日宣布的基于Linux平台的开源手机操作系统的名称,该平台由操作系统.中间件.用户界面和应用软件组成,号称是首个为移动终端打造的真正开放和完整的移动软件. 架构详解 下 ...

  8. 详解 Node + Redux + MongoDB 实现 Todolist

    前言 为什么要使用 Redux? 组件化的开发思想解放了繁琐低效的 DOM 操作,以 React 来说,一切皆为状态,通过状态可以控制视图的变化,然后随着应用项目的规模的不断扩大和应用功能的不断丰富, ...

  9. ASP.NET Core Web服务器 Kestrel和Http.sys 特性详解

    ASP.NET Core Web服务器 Kestrel和Http.sys 特性详解 1.1. 名词解释 1.2. Kestrel基本工作原理 1.2.1. Kestrel的基本架构 1.2.2. Ke ...

随机推荐

  1. sails 相关文章

    Node 框架之sails   http://cnodejs.org/topic/555c3c82e684c4c8088a0ca1

  2. Windows 7 USB DVD Download Tool 制作的U盘无法启动安装Windows7 SP1

    以前用此工具安装Windows7一直正常,未遇到不能启动安装的问题.Windows7 SP1出来后,用此工具制作安装多台机器均不能引导安装(品牌机和兼容机均是如此 ),要么停留在光标闪烁的状态,要么就 ...

  3. JTable demo

    简单讲就是在没有使用layout manager的时候用setSize,在使用了layout manager 的时候用setPreferredSize 并且setPreferredSize通常和set ...

  4. virus.win32.parite.H查杀病毒的方法

    virus.win32.parite.H病毒的查杀方法 昨天电脑中了virus.win32.parite.H病毒,搞了2个多小时最终搞定了.以下记录下我的解决方法. 第一步:下载Win32.Parit ...

  5. [LeetCode290]Word Pattern

    题目: Given a pattern and a string str, find if str follows the same pattern. Here follow means a full ...

  6. [LeetCode258] Add Digits 非负整数各位相加

    题目: Given a non-negative integer num, repeatedly add all its digits until the result has only one di ...

  7. 采用UltraISO制作U菜Win7安装盘,显现&quot;File not find /BOOT/CDMENU.EZB.ezb&quot;错误

    一机公司Win7动力password不知道.这台机器也很慢, 刷新Win7,运用32位Ghost设备ISO档.从机U之后启动盘,演出 "File not find /BOOT/CDMENU. ...

  8. 妙用perfmon Alert抓dump

    抓dump文件,经常是解决众多疑难杂症的不二手段.但是很多时候,我们没办法抓.比如说 几秒内的线程数暴涨200个,然后迅速回落 程序跑了两天,内存涨到某个数字就自己OOM了 原因不外乎都是时间短,没有 ...

  9. CentOS7 防火墙 firewall-cmd

    最小化安装 CentOS7 后,很多端口默认是不打开的,需要通过  firewall-cmd   把这些端口打开. 检查防火墙状态 # firewall-cmd --state running 关闭防 ...

  10. 【Flume】flume于transactionCapacity和batchSize进行详细的分析和质疑的概念

    我不知道你用flume读者熟悉无论这两个概念 一开始我是有点困惑,? 没感觉到transactionCapacity的作用啊? batchSize又是干啥的啊? -- -- 带着这些问题,我们深入源代 ...