[ASP.NET Core开发实战]基础篇03 中间件
什么是中间件
中间件是一种装配到应用管道,以处理请求和响应的组件。每个中间件:
- 选择是否将请求传递到管道中的下一个中间件。
- 可在管道中的下一个中间件前后执行。
ASP.NET Core请求管道包含一系列请求委托,依次调用。工作原理:
PS:类似于ASP.NET里的Handler(处理程序)和Module(模块)。
HTTP模块和处理程序的工作原理:
创建中间件管道
Run委托不会收到next参数。第一个Run委托始终为终端,用于终止管道。Run是一种约定。
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
}
Use将多个请求委托链接在一起。next参数表示管道中的下一个委托。可通过不调用next参数使用管道短路。通常可在上一个委托前后执行操作:
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
// Do work that doesn't write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
});
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
}
}
警告:在向客户端发送Response后,不要再调用next.Invoke。Response启动后,针对HttpResponse的修改会引起异常。调用next后写入Response正文:
- 可能导致违反协议。例如,写入的长度超过规定的Content-Length。
- 可能损坏正文格式。例如,向CSS文件中写入HTML页脚。
HasStarted是一个有用的提示,指示是否已发送标头或已写入正文。
中间件顺序
中间件是通过请求管道逐一执行的,通过Startup.Configure方法里添加中间件的顺序来定义了请求管道的顺序。
如上图所示,自定义的中间件放在Authorization中间件和Endpoint之间。
以下是ASP.NET Core项目默认配置的中间件顺序:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy();
app.UseRouting();
// app.UseRequestLocalization();
// app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
// app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
中间件分支
当我们需要对某些请求进行特定处理时,可以通过创建管道分支来实现。Map扩展方法用于创建管道分支。
public class Startup
{
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}
private static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/map1", HandleMapTest1);
app.Map("/map2", HandleMapTest2);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}
MapWhen是用于对特定谓词的结果来创建请求管道分支。相比于Map(只能对针对特定的Url)更加高级。
public class Startup
{
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}
public void Configure(IApplicationBuilder app)
{
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}
UseWhen也是用于对特定谓词的结果来创建请求管道分支。但与MapWhen不同的是,如果这个分支不发生短路或包含终端中间件,则会重新加入主管道。
public class Startup
{
private readonly ILogger<Startup> _logger;
public Startup(ILogger<Startup> logger)
{
_logger = logger;
}
private void HandleBranchAndRejoin(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
var branchVer = context.Request.Query["branch"];
_logger.LogInformation("Branch used = {branchVer}", branchVer);
// Do work that doesn't write to the Response.
await next();
// Do other work that doesn't write to the Response.
});
}
public void Configure(IApplicationBuilder app)
{
app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranchAndRejoin);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from main pipeline.");
});
}
}
自定义中间件
下面以多语言版本功能为例,介绍如何编写自定义中间件。
多语言版本功能指根据所在区域,以所在区域的语言显示网站内容。例如在中国以汉字显示、在美国以英文显示、在俄罗斯以俄文显示。
下面代码是直接在Startup.Configure编写,通过参数culture手动指定语言版本,例如请求https://localhost:5001/?culture=zh-cn
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline
await next();
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}
一般来说,编写自定义中间件使用类,既符合单一职责原则,也更方便管理。
using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks;
namespace Culture
{
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
}
中间件类必须满足:
- 有RequestDelegate类型参数的公共构造函数。
- 有名字为Invoke或InvokeAsync的方法,并且方法要满足:
- 返回值是Task。
- 方法参数第一个是HttpContext类型。
在实际开发中,中间件会用到其他服务,由于中间件是在应用启动时创建的,生命周期是Singleton的,不与其他依赖注入的服务共享生命周期。如果需要使用依赖注入,可以通过Invoke或InvokeAsync方法注入。
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
// IMyScopedService is injected into Invoke
public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
{
svc.MyProperty = 1000;
await _next(httpContext);
}
}
最后,我们习惯性在Startup.Configure中,通过扩展方法来注册中间件。
using Microsoft.AspNetCore.Builder;
namespace Culture
{
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
}
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseRequestCulture();
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}
参考资料
[ASP.NET Core开发实战]基础篇03 中间件的更多相关文章
- [ASP.NET Core开发实战]基础篇02 依赖注入
ASP.NET Core的底层机制之一是依赖注入(DI)设计模式,因此要好好掌握依赖注入的用法. 什么是依赖注入 我们看一下下面的例子: public class MyDependency { pub ...
- [ASP.NET Core开发实战]基础篇01 Startup
Startup,顾名思义,就是启动类,用于配置ASP.NET Core应用的服务和请求管道. Startup有两个主要作用: 通过ConfigureServices方法配置应用的服务.服务是一个提供应 ...
- [ASP.NET Core开发实战]基础篇06 配置
配置,是应用程序很重要的组成部分,常常用于提供信息,像第三方应用登录钥匙.上传格式与大小限制等等. ASP.NET Core提供一系列配置提供程序读取配置文件或配置项信息. ASP.NET Core项 ...
- [ASP.NET Core开发实战]基础篇05 服务器
什么是服务器 服务器指ASP.NET Core应用运行在操作系统上的载体,也叫Web服务器. Web服务器实现侦听HTTP请求,并以构建HttpContext的对象发送给ASP.NET Core应用. ...
- [ASP.NET Core开发实战]基础篇04 主机
主机定义 主机是封闭应用资源的对象. 设置主机 主机通常由 Program 类中的代码配置.生成和运行. HTTP项目(ASP.NET Core项目)创建泛型主机: public class Prog ...
- 2月送书福利:ASP.NET Core开发实战
大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...
- [ASP.NET Core开发实战]开篇词
前言 本系列课程文章主要是学习官方文档,再输出自己学习心得,希望对你有所帮助. 课程大纲 本系列课程主要分为三个部分:基础篇.实战篇和部署篇. 希望通过本系列课程,能让大家初步掌握使用ASP.NET ...
- ASP.NET Core 2.2 基础知识(二) 中间件
中间件是一种装配到应用管道以处理请求和相应的软件.每个软件都可以: 1.选择是否将请求传递到管道中的下一个组件; 2.可在调用管道中的下一个组件前后执行工作. 管道由 IApplicationBuil ...
- 《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发
<ASP.NET Core项目开发实战入门>从基础到实际项目开发部署带你走进ASP.NET Core开发. ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 ...
随机推荐
- 一本通 1783 矩阵填数 状压dp 容斥 计数
LINK:矩阵填数 刚看到题目的时候感觉是无从下手的. 可以看到有n<=2的点 两个矩形. 如果只有一个矩形 矩形外的方案数容易计算考虑 矩形内的 必须要存在x这个最大值 且所有值<=x. ...
- Centos7 如何通过win10 的远程桌面连接进行远程访问
首先,如果安装测centos7是已经安装了GNOME 或者 KDE 桌面, 则只需要再安装xrdp就可以了. 直接通过yum install xrdp 是不行的,因为xrdp 不在默认源中 先配置 ...
- python2.3嵌套if结构:
#案例:存款100万的请款下,买宝马:老爸资助大于50万买宝马740:大于30万买宝马520:小于20万宝马320.存款大于50万小于100万买丰田:大于20万小于50万买二手车:小于20万自行车! ...
- 打开IDEA后tomcat不能用,Cannot load project of unknown project type,无法加载类或者项目
这一问题在网络中有比较统一的解决方法,我这个也是按这个方法解决的. 问题出现的前提和原因: 一个运行正常项目,我关闭后第二天打开发现tomcat不能用了. 解决方法: 我查了一下,这是一个IDEA软件 ...
- day24:多态&魔术方法__new__&单态模式
目录 1.多态 2.__new__魔术方法 2.1 关于魔术方法__new__ 2.2 基本语法 2.3 __new__ 触发时机快于构造方法 2.4 __new__ 和 __init__ 参数一一对 ...
- Nexus3 上传的文件在哪里
上传文件 ojdbc7.jar,上传步骤略. 服务器上默认的文件存放路径是: nexus/sonatype-work/nexus3/blobs/default/content/ 一堆文件夹,根据时间确 ...
- ECS7天实践进阶训练营Day2:基于阿里云ECS部署MediaWiki
一.概述 MediaWiki是全球最著名的开源Wiki程序,运行于PHP+MySQL环境,MediaWiki从2002年被作为维基百科的系统软件,并由大量其他应用实例(例如萌娘百科),因此MediaW ...
- SSH安全免密码登录:ssh key
ssh key 使用非对称加密方式生成公钥和私钥 私钥存放在本地~/.ssh目录 公钥可以对外公开,放在服务器的~/.ssh/authorized_keys 1.linux平台生成ssh key ss ...
- 企业级Gitlab-ci实践
前言 吐槽一波 2020年6月2号刚入职公司时,第一感觉是集群环境是个大坑!内网一套,公网一套.内网采用单节点Kubernetes,公网采用aliyun托管的X节点Kubernetes(还有节点是2C ...
- C - 一个C语言猜字游戏
下面是一个简陋的猜字游戏,玩了一会儿,发现自己打不过自己写的游戏,除非赢了就跑,最高分没有过1000. 说明:srand(time(NULL))和rand(),srand,time和rand都是函数, ...