5.5 中间件 (Middleware)
5.5.1 关于中间件
中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在管道中的下一个组件前后执行工作。
- 请求委托用于生成请求管道。 请求委托处理每个
HTTP
请求。
一句话总结:中间件是比筛选器更底层,更上游的面向切面技术,其性能最高,可处理的应用范围远比过滤器广,如实现网关,URL
转发,限流等等。
本章节暂不考虑将中间件展开讲,想了解更多知识可阅读官方文档 【ASP.NET Core - 中间件】
5.5.2 常见中间件
5.5.2.1 所有请求返回同一个结果
app.Run(async context =>
{
await context.Response.WriteAsync("Hello world!");
});
5.5.2.2 拦截所有请求(可多个)
app.Use(async (context, next) =>
{
// 比如设置统一头
context.Response.Headers["framework"] = "Furion";
// 执行下一个中间件
await next.Invoke();
});
// 多个
app.Use(...);
5.5.2.3 特定路由中间件(可多个)
app.Map("/hello", app => {
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
});
app.Map("/hello/say", app => {
// ....
});
5.5.2.4 嵌套路由中间件(可多个)
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});
更多例子查看官方文档 https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0
5.5.3 自定义中间件
自定义中间件有多种方式,最简单的是通过 app.Use
方式,另外还支持独立类定义方式。
5.5.3.1 app.Use
方式 (不推荐)
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;
}
// 调用下一个中间件
await next(context);
});
5.5.3.2 独立类
方式(推荐)
独立类的方式是目前最为推荐的方式,拓展性强,维护性高,如:
- 定义中间件,建议以
Middleware
结尾:
using System.Globalization;
namespace Middleware.Example;
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;
}
// 调用下一个中间件
await _next(context);
}
}
- 添加中间件拓展类
定义了中间件之后,需要创建这个中间件的拓展类,中间件拓展方法建议以 Use
开头,如:
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
- 在
Startup.cs
中使用
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ... 其他中间件
app.UseRequestCulture();
// ... 其他中间件
}
5.5.3.3 配置更多参数
默认情况下,自定义独立类中间件构造函数只有一个 RequestDelegate
参数,除此之后,还可以注入服务接口/类,另外还支持传入任何其他类型。
- 服务类型参数
using System.Globalization;
namespace Middleware.Example;
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestCultureMiddleware> _logger;
public RequestCultureMiddleware(RequestDelegate next
, ILogger<RequestCultureMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// 其他代码
_logger.LogInformation("...");
// 调用下一个中间件
await _next(context);
}
}
- 非服务类型参数
除此之外,还可以添加 非服务参数
参数,但必须是声明在服务参数后。
using System.Globalization;
namespace Middleware.Example;
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestCultureMiddleware> _logger;
public RequestCultureMiddleware(RequestDelegate next
, ILogger<RequestCultureMiddleware> logger
, int age
, string name)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// 其他代码
_logger.LogInformation("...");
// 调用下一个中间件
await _next(context);
}
}
之后还需要修改中间件拓展类:
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder, int age, string name)
{
return builder.UseMiddleware<RequestCultureMiddleware>(new object[] {age, name });
}
}
使用:
app.UseRequestCulture(30, "百小僧");
5.5.4 中间件顺序
中间件是有执行顺序的,而且是先注册的先执行,无法通过其他方式更改,参考下图:
5.5.5 依赖注入/解析服务
中间件有两种方式注入服务,一种是通过构造函数注入,一种是通过 httpContext.RequestServices
方式解析。
5.5.5.1 构造函数方式
using System.Globalization;
namespace Middleware.Example;
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestCultureMiddleware> _logger;
public RequestCultureMiddleware(RequestDelegate next
, ILogger<RequestCultureMiddleware> logger
, IHostEnvironment hostEnvironment)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// 其他代码
// 调用下一个中间件
await _next(context);
}
}
5.5.5.2 httpContext.RequestServices
方式
HttpContext
提供了 RequestServices
属性方便解析服务。
using System.Globalization;
namespace Middleware.Example;
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestCultureMiddleware> _logger;
// 构造函数注册
public RequestCultureMiddleware(RequestDelegate next
, ILogger<RequestCultureMiddleware> logger
, IHostEnvironment hostEnvironment)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// 通过 context.RequestServices 解析
var repository = context.RequestServices.GetService<IRepository>();
// 调用下一个中间件
await _next(context);
}
}
5.5.6 常见问题
由于中间件是比较原始的切面方式,有时候我们需要获取终点路由的特性或者其他信息,则需要一点技巧:
// 获取终点路由特性
var endpointFeature = context.Features.Get<IEndpointFeature>();
// 获取是否定义了特性
var attribute = endpointFeature?.Endpoint?.Metadata?.GetMetadata<YourAttribute>()
要想上面操作有效,也就是不为 null
,需要满足以下条件,否则 endpointFeature
返回 null
。
- 启用端点路由
AddControllers()
而不是AddMvc()
UseRouting()
和UseEndpoints()
之间调用你的中间件
5.5.7 了解更多
想了解更多中间件知识可阅读官方文档 【ASP.NET Core - 中间件】
5.5.8 反馈与建议
给 Furion 提 Issue。