❤️ 关注 Furion 微信公众号有惊喜哦!
🫠 遇到问题了
Skip to main content
⭐️ 开通 VIP 服务仅需 499 元/年,尊享 365 天项目无忧23 立即开通23 ⭐️
特别赞助

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 方式 (不推荐)

Starup.cs
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