ASP.NET Core 中文文档 第三章 原理(2)中间件
原文:Middleware
作者:Steve Smith、Rick Anderson
翻译:刘怡(AlexLEWIS)
校对:许登洋(Seay)
章节:
什么是中间件
中间件是用于组成应用程序管道来处理请求和响应的组件。管道内的每一个组件都可以选择是否将请求交给下一个组件、并在管道中调用下一个组件之前和之后执行某些操作。请求委托被用来建立请求管道,请求委托处理每一个 HTTP 请求。
请求委托通过使用 IApplicationBuilder 类型的 Run、Map 以及 Use 扩展方法来配置,并在 Startup 类中传给 Configure 方法 。每个单独的请求委托都可以被指定为一个内嵌匿名方法,或其定义在一个可重用的类中。这些可重用的类被称作 中间件 或 中间件组件。每个位于请求管道内的中间件组件负责调用管道中下一个组件,或适时短路调用链。
Migrating HTTP Modules to Middleware 解释了请求管道在 ASP.NET Core 和之前版本之间的区别,并提供了更多中间件样例。
用 IApplicationBuilder 创建中间件管道
ASP.NET 请求管道由一系列的请求委托所构成,它们一个接着一个被调用,如图所示(该执行线程按黑色箭头的顺序执行):

每个委托在下一个委托之前和之后都有机会执行操作。任何委托都能选择停止传递到下一个委托,转而自己处理该请求。这被叫做请求管道的短路,而且是一种有意义的设计,因为它可以避免不必要的工作。比方说,一个授权(authorization)中间件只有在通过身份验证之后才调用下一个委托,否则它就会被短路并返回 “Not Authorized” 的响应。异常处理委托需要在管道的早期被调用,这样它们就能够捕捉到发生在管道内更深层次出现的异常了。
你可以看一下 Visual Studio 2015 附带的默认 Web 站点模板关于请求管道设置的例子。Configure 方法增加了下列这些中间件组件:
- 错误处理(同时针对于开发环境和非开发环境)
- 静态文件服务器
- 身份验证
- MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();//手工高亮
app.UseDatabaseErrorPage();//手工高亮
app.UseBrowserLink();//手工高亮
}
else
{
app.UseExceptionHandler("/Home/Error");//手工高亮
}
app.UseStaticFiles();//手工高亮
app.UseIdentity();//手工高亮
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseMvc(routes =>//手工高亮
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
上面的代码中(在非开发环境时),UseExceptionHandler 是第一个被加入到管道中的中间件,因此将会捕获之后调用中出现的任何异常。
静态文件模块 不提供授权检查,由它提供的任何文件,包括那些位于wwwroot 下的文件都是公开的可被访问的。如果你想基于授权来提供这些文件:
- 将它们存放在 wwwroot 外面以及任何静态文件中间件都可访问得到的目录。
- 利用控制器操作来判断授权是否允许,如果允许则通过返回 FileResult 来提供它们。
被静态文件模块处理的请求会在管道中被短路(参见 Working with Static Files)。如果该请求不是由静态文件模块处理,那么它就会被传给 Identity 模块 执行身份验证。如果未通过身份验证,则管道将被短路。如果请求的身份验证没有失败,则管道的最后一站是 MVC 框架。
注意
你添加中间件组件的顺序通常会影响到它们处理请求的顺序,然后在响应时则以相反的顺序返回。这对应用程序安全、性能和功能很关键。在上面的代码中,静态文件中间件 在管道的早期被调用,这样就能处理并及时短路管道,以避免请求走到不必要的组件中。身份验证中间件被添加在任何需要身份认证的处理请求的前面。异常处理必须被注册在其它中间件之前以便捕获其它组件的异常。
最简单的 ASP.NET 应用程序是使用单个请求委托来处理所有请求。事实上在这种情况下并不存在所谓的“管道”,调用单个匿名函数以相应每个 HTTP 请求。
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
第一个 App.Run 委托中断了管道。在下面的例子中,只有第一个委托(“Hello, World!”)会被运行。
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");//手工高亮
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World, Again!");
});
将多个请求委托彼此链接在一起;next 参数表示管道内下一个委托。通过 不 调用 next 参数,你可以中断(短路)管道。你通常可以在执行下一个委托之前和之后执行一些操作,如下例所示:
public void ConfigureLogInline(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
loggerfactory.AddConsole(minLevel: LogLevel.Information);
var logger = loggerfactory.CreateLogger(_environment);
app.Use(async (context, next) =>//手工高亮
{
logger.LogInformation("Handling request.");
await next.Invoke();//手工高亮
logger.LogInformation("Finished handling request.");
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from " + _environment);//手工高亮
});
}
警告
应当避免在修改了HttpResponse之后还调用管道内下一个会修改响应的组件,从而导致它被送到客户端处。
提示
当应用程序运行的环境设置为LogInline时,这个ConfigureLogInline方法就会被调动。要了解更多请访问环境 Working with Multiple Environments 一章。本文剩下的篇幅将使用变化的Configure[Environment]来展示不同的选项。 Visual Studio 中运行示例代码的最简单办法是使用web命令,该命令由 project.json 文件所配置。也可参考 Application Startup 。
在上例中,调用 await next.Invoke() 将会调用下一个委托await context.Response.WriteAsync("Hello from " + _environment);。客户端将收到预期的响应(“Hello from LogInline”),同时服务端这边的控制台将先后输出如下信息:

Run,Map 与 Use
你可以使用 Run、Map 和 Use 配置 HTTP 管道。Run 方法将会短路管道(因为它不会调用 next 请求委托)。因此,Run 应该只能在你的管道尾部被调用。Run 是一种惯例,有些中间件组件可能会暴露他们自己的 Run[Middleware] 方法,而这些方法只能在管道末尾处运行。下面这两个中间件等价的,其中有用到 Use 的版本没有使用 next 参数:
public void ConfigureEnvironmentOne(IApplicationBuilder app)
{
app.Run(async context =>//手工高亮
{
await context.Response.WriteAsync("Hello from " + _environment);
});
}
public void ConfigureEnvironmentTwo(IApplicationBuilder app)
{
app.Use(async (context, next) =>//手工高亮
{
await context.Response.WriteAsync("Hello from " + _environment);
});
}
注意
IApplicationBuilder 接口向外暴露了一个Use方法,因此从技术上来说它们并不完全是 扩展 方法。
我们已经看了几个关于如何通过 Use 构建请求管道的例子,同时约定了 Map*扩展被用于分支管道。当前的实现已支持基于请求路径或使用谓词来进入分支。Map 扩展方法用于匹配基于请求路径的请求委托。Map 只接受路径,并配置单独的中间件管道的功能。在下例中,任何基于路径 /maptest 的请求都会被管道中所配置的 HandleMapTest 方法所处理。
private static void HandleMapTest(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test Successful");
});
}
public void ConfigureMapping(IApplicationBuilder app)
{
app.Map("/maptest", HandleMapTest);//手工高亮
}
注意
当使用了Map,每个请求所匹配的路径段将从HttpRequest.Path中移除,并附加到HttpRequest.PathBase中。
除基于路径的映射外,MapWhen 方法还支持基于谓词的中间件分支,允许以非常灵活的方式构建单独的管道。任何 Func<HttpContext, bool> 类型的谓语都被用于将请求映射到新的管到分支。在下例中使用了一个简单的谓词来检测查询字符串变量 branch 是否存在:
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Branch used.");//手工高亮
});
}
public void ConfigureMapWhen(IApplicationBuilder app)
{
app.MapWhen(context => {//手工高亮
return context.Request.Query.ContainsKey("branch");//手工高亮
}, HandleBranch);//手工高亮
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from " + _environment);
});
}
使用了上述设置后,任何包含请求字符 branch 的请求将使用定义于HandleBranch 方法内的管道(其响应就将是“Branch used.”)。其他请求(即没有为 branch 定义查询字符串值)将被第 17 行所定义的委托处理。
你也可以嵌套映射:
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a"
//...
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b"
//...
});
});
内置中间件
ASP.NET 带来了下列中间件组件:
| 中间件 | 描述 |
|---|---|
| 身份验证(Authentication) | 提供身份验证支持。 |
| 跨域资源共享(CORS) | 配置跨域资源共享。CORS 全称为 Cross-Origin Resource Sharing。 |
| 路由(Routing) | 定义和约定请求路由。 |
| 会话(Session) | 提供对管理用户会话(session)的支持。 |
| 静态文件 | 提供对静态文件服务于目录浏览的支持。 |
编写中间件
CodeLabs 中间件教程 提供了一个清晰介绍用于编写中间件。
对于更复杂的请求处理功能,ASP.NET 团队推荐在他们自己的类中实现中间件,并暴露 IApplicationBuilder 扩展方法,这样就能通过 Configure 方法来被调用。之前演示的简易日志中间件就能被转换为一个中间件类(middleware class):只要在其构造函数中获得下一个 RequestDelegate 并提供一个 Invoke方法,如下所示:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace MiddlewareSample
{
public class RequestLoggerMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public RequestLoggerMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)//手工高亮
{
_next = next;
_logger = loggerFactory.CreateLogger<RequestLoggerMiddleware>();
}
public async Task Invoke(HttpContext context)//手工高亮
{
_logger.LogInformation("Handling request: " + context.Request.Path);
await _next.Invoke(context);
_logger.LogInformation("Finished handling request.");
}
}
}
中间件遵循 显式依赖原则 并在其构造函数中暴露所有依赖项。中间件能够利用到 UseMiddleware 扩展方法的优势,直接通过它们的构造函数注入服务,就像下面的例子所示。依赖注入服务是自动完成填充的,扩展所用到的 params 参数数组被用于非注入参数。
public static class RequestLoggerExtensions
{
public static IApplicationBuilder UseRequestLogger(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestLoggerMiddleware>();//手工高亮
}
}
通过使用扩展方法和相关中间件类,Configure 方法变得非常简洁和高可读性。
public void ConfigureLogMiddleware(IApplicationBuilder app,
ILoggerFactory loggerfactory)
{
loggerfactory.AddConsole(minLevel: LogLevel.Information);
app.UseRequestLogger();//手工高亮
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from " + _environment);
});
}
尽管 RequestLoggerMiddleware 在其构造函数中需要 ILoggerFactory 参数,但无论是 Startup 类还是 UseRequestLogger 扩展方法都不需要显式依赖之。相反,它将自动地通过内置的 UseMiddleware<T> 来执行依赖注入以提供之。
测试中间件(通过给 LogMiddleware 设置 Hosting:Environment 环境变量)会输出下图的结果(当时用了 WebListener 时):

注意
UseStaticFiles 扩展方法(该方法会创建 StaticFileMiddleware)同样也使用了UseMiddleware<T>。所以除了StaticFileOptions参数被传入之外,构造函数的其他参数都由UseMiddleware<T>和依赖注入所提供。
扩展资源
ASP.NET Core 中文文档 第三章 原理(2)中间件的更多相关文章
- ASP.NET Core 中文文档 第三章 原理(6)全球化与本地化
原文:Globalization and localization 作者:Rick Anderson.Damien Bowden.Bart Calixto.Nadeem Afana 翻译:谢炀(Kil ...
- ASP.NET Core 中文文档 第三章 原理(1)应用程序启动
原文:Application Startup 作者:Steve Smith 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay) ASP.NET Core 为你的应用程 ...
- ASP.NET Core 中文文档 第三章 原理(13)管理应用程序状态
原文:Managing Application State 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:高嵩 在 ASP.NET Core 中,有多种途径可以对应用程序的状态进行 ...
- ASP.NET Core 中文文档 第三章 原理(3)静态文件处理
原文:Working with Static Files 作者:Rick Anderson 翻译:刘怡(AlexLEWIS) 校对:谢炀(kiler398).许登洋(Seay).孟帅洋(书缘) 静态文 ...
- ASP.NET Core 中文文档 第三章 原理(10)依赖注入
原文:Dependency Injection 作者:Steve Smith 翻译:刘浩杨 校对:许登洋(Seay).高嵩 ASP.NET Core 的底层设计支持和使用依赖注入.ASP.NET Co ...
- ASP.NET Core 中文文档 第三章 原理(11)在多个环境中工作
原文: Working with Multiple Environments 作者: Steve Smith 翻译: 刘浩杨 校对: 孟帅洋(书缘) ASP.NET Core 介绍了支持在多个环境中管 ...
- ASP.NET Core 中文文档 第三章 原理(17)为你的服务器选择合适版本的.NET框架
原文:Choosing the Right .NET For You on the Server 作者:Daniel Roth 翻译:王健 校对:谢炀(Kiler).何镇汐.许登洋(Seay).孟帅洋 ...
- ASP.NET Core 中文文档 第三章 原理(4)路由
原文:Routing 作者:Ryan Nowak.Steve Smith. Rick Anderson 翻译:张仁建(Stoneqiu) 校对:许登洋(Seay).谢炀(kiler398).孟帅洋(书 ...
- ASP.NET Core 中文文档 第三章 原理(7)配置
原文:Configuration 作者:Steve Smith.Daniel Roth 翻译:刘怡(AlexLEWIS) 校对:孟帅洋(书缘) ASP.NET Core 支持多种配置选项.应用程序配置 ...
随机推荐
- HTML5 介绍
本篇主要介绍HTML5规范的内容和页面上的架构变动. 目录 1. HTML5介绍 1.1 介绍 1.2 内容 1.3 浏览器支持情况 2. 创建HTML5页面 2.1 <!DOCTYPE> ...
- Xamarin与Visual stuido2015离线安装包分享
最近看见大伙留言才知道国内安装Xamarin开发原来这么艰辛啊! 第一:网速不快 第二:Android SDK下载受限 等等... 鉴于这些原因,特写下这篇文章以及分享打包好的离线包以帮助大家尽快体验 ...
- 源码分析netty服务器创建过程vs java nio服务器创建
1.Java NIO服务端创建 首先,我们通过一个时序图来看下如何创建一个NIO服务端并启动监听,接收多个客户端的连接,进行消息的异步读写. 示例代码(参考文献[2]): import java.io ...
- 【知识必备】ezSQL,最好用的数据库操作类,让php操作sql更简单~
最近用php做了点小东东,用上了ezSQL,感觉真的很ez,所以拿来跟大家分享一下~ ezSQL是一个非常好用的PHP数据库操作类.著名的开源博客WordPress的数据库操作就使用了ezSQL的My ...
- CSS 3学习——transition 过渡
以下内容根据官方规范翻译以及自己的理解整理. 1.介绍 这篇文档介绍能够实现隐式过渡的CSS新特性.文档中介绍的CSS新特性描述了CSS属性的值如何在给定的时间内平滑地从一个值变为另一个值. 2.过渡 ...
- [Nginx笔记]关于线上环境CLOSE_WAIT和TIME_WAIT过高
运维的同学和Team里面的一个同学分别遇到过Nginx在线上环境使用中会遇到TIME_WAIT过高或者CLOSE_WAIT过高的状态 先从原因分析一下为什么,问题就迎刃而解了. 首先是TIME_WAI ...
- js闭包 和 prototype
function test(){ var p=200; function q(){ return p++; } return q; } var s = test(); alert(s()); aler ...
- slf4j中的MDC
slf4j中MDC是什么鬼 slf4j除了trace.debug.info.warn.error这几个日志接口外,还可以配合MDC将数据写入日志.换句话说MDC也是用来记录日志的,但它的使用方式与使用 ...
- Javascript学习笔记
Javascript 2016年12月19日整理 JS基础 Chapter1 JS是一门运行在浏览器客户端的脚本编程语言,前台语言 组成部分 1. ECMAscript JS标准 2. DOM 通过J ...
- 【SAP业务模式】之ICS(六):发票输出类型
这篇开始主要讲述发票输出类型: 首先我们新建一个发票类型,用于公司间的发票MIV,而标准的发票类型还是F2保持不变: 一.新建发票类型: 目录:SPRO-销售与分销-出具发票-开票凭证-定义出具发票类 ...