参考:https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-8.0
1、什么是中间件
中间件是一种装配到应用管道以处理请求(Request)和响应(Response)的组件。 每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在管道中的下一个组件前后执行工作。
请求委托(Request delegates)用于生成请求管道。 请求委托处理每个 HTTP 请求。
1.1、中间件的工作原理
多个中间件按照顺序关系形成请求管道,用来处理HTTP请求和响应。中间件本质上是一个用来处理HTTP请求与响应的类。
HTTP请求(Request)进入请求管道(Request Pipeline),将会创建一个对象,用于描述请求(Http Request)信息,也用于返回最终HTTP响应(Response)。
请求从一端进入,按照添加顺序通过各个中间件(Middleware),每个中间件都可以对传入的请求进行一些操作然后 传入下一个中间件 或 直接返回。而对于返回响应(HTTP Response)也会遍历进来时所经过的中间件,顺序与请求时的正好相反。
注意:
next()
前的代码会在管道进入的阶段执行,next后的代码会在管道返回阶段进行执行。- 如果中间件最终没有返回HTTP Response, 将会提示HTTP 404 Not Found。
1.2、中间件顺序
下图显示了 ASP.NET Core MVC 和 Razor Pages 应用的完整请求处理管道。图中的“终结点”中间件为相应的应用类型(MVC 或 Razor Pages)执行筛选器管道。
MVC中间件:
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//异常处理中间件
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
//静态文件中间件,为提供静态文件(如HTML、CSS、JavaScript、图像等)和目录浏览提供支持。
//如果请求与文件匹配,则为终端。
app.UseStaticFiles();
//路由中间件
app.UseRouting();
//权限中间件
app.UseAuthorization();
//终端中间件,用于处理端点,如控制器和视图
app.UseEndpoints(endpoints =>
{
//定义默认的路由规则,指定了控制器、操作和可选的ID参数。
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
2、Map、Use、Run
Map:用来定义一个管道可以处理哪些请求。
Use和Run:用来定义管道,一个管道由若干个Use和一个Run组成,每个Use引入一个中间件,而Run用来执行最终的核心应用逻辑 。
.Net Core中多种方式注册中间件:
- Use:以委托的方式注册中间件。
- UseMiddleware:以类型的方式注册中间件,T表示中间件的类型。
- Map:将特定的请求地址(path)与中间件绑定,即path匹配时执行该中间件。
- MapWhen:定义一个逻辑判断委托,当判断为true时执行指定的中间件。
- Run:表示一个断路的中间件,是执行并返回给上游的中间件,无后续中间件。
2.1、Map对中间件管道进行分支
Map 扩展用作约定来创建管道分支。 Map
基于给定请求路径的匹配项来创建请求管道分支。 如果请求路径以给定路径开头,则执行分支。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Map("/map1", HandleMapTest1);
app.Map("/map2", HandleMapTest2);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
app.Run();
static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
}
static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
}
请求 | 响应 |
---|---|
localhost:1234 | Hello from non-Map delegate. |
localhost:1234/map1 | Map Test 1 |
localhost:1234/map2 | Map Test 2 |
localhost:1234/map3 | Hello from non-Map delegate. |
2.2、Use链接
用 Use 将多个请求委托链接在一起。 next
参数表示管道中的下一个委托。 可通过不调用 next
参数使管道短路。 通常可在 next
委托前后执行操作。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 向应用程序的请求管道中添加一个Func委托,这个委托其实就是所谓的中间件。
// context参数是HttpContext,表示HTTP请求的上下文对象
// next参数表示管道中的下一个中间件委托,如果不调用next,则会使管道短路
// 用Use可以将多个中间件链接在一起
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("MiddleWare(1)-In\n");
await next();
await context.Response.WriteAsync("MiddleWare(1)-Out\n");
});
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("MiddleWare(2)-In\n");
await next();
await context.Response.WriteAsync("MiddleWare(2)-Out\n");
});
// Run方法向应用程序的请求管道中添加一个RequestDelegate委托
// 放在管道最后面,作为终端中间件
app.Run(async (context) =>
{
await context.Response.WriteAsync("MiddleWare(3)\n");
});
}
////执行结果:
//MiddleWare(1)-In
//MiddleWare(2)-In
//MiddleWare(3)
//MiddleWare(2)-Out
//MiddleWare(1)-Out
3、自定义中间件
3.1、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;
}
// Call the next delegate/middleware in the pipeline.
await next(context);//参数可省略,ASP.NET自动给参数。
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"CurrentCulture.DisplayName: {CultureInfo.CurrentCulture.DisplayName}");
});
app.Run();
3.2、自定义中间件类
- 具有类型为 RequestDelegate 的参数的公共构造函数。
- 名为
Invoke
或InvokeAsync
的公共方法。 此方法必须:- 返回
Task
。 - 接受类型 HttpContext 的第一个参数。
- 返回
构造函数和 Invoke
/InvokeAsync
的其他参数由依赖关系注入 (DI) 填充。
以下示例中间件通过查询字符串设置当前请求的区域性:
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;
}
// Call the next delegate/middleware in the pipeline.
await _next(context);
}
}
通常,创建扩展方法以通过 IApplicationBuilder 公开中间件:
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
调用中间件:
app.UseRequestCulture();
4、Middleware和Filter的区别
比较:
- 中间件: 中间件是一种装配到应用程序管道以处理HTTP请求和响应的组件。每个组件可以选择是否将请求传递到管道中的下一个组件,并可以在管道中的任意点添加。
- 过滤器: 过滤器是在ASP.NET Core MVC或Web API应用程序中用于拦截操作方法执行的组件。它们可以在执行前后执行逻辑,例如验证、日志记录、处理异常等。
中间件示例:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.Use(async (context, next) => {
// 在调用下一个中间件之前可以做一些事情
await context.Response.WriteAsync("Before next middleware \r\n");
// 调用下一个中间件
await next();
// 在调用下一个中间件之后可以做一些事情
await context.Response.WriteAsync("After next middleware \r\n");
});
// ... 其他中间件配置
}
过滤器示例:
public class MySampleFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// 在动作方法执行之前可以做一些事情
}
public void OnActionExecuted(ActionExecutedContext context)
{
// 在动作方法执行之后可以做一些事情
}
}
[ServiceFilter(typeof(MySampleFilter))]
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
}
总结:
- 中间件和过滤器都是一种AOP的思想,都用于处理HTTP请求和响应。但是它们处理HTTP请求的方式不同:中间件是基于管道的方式,而过滤器是基于特定动作或控制器的方式。
- 中间件可以用于那些不是特定于业务领域的逻辑,以及需要在每个请求或大多数请求中发生的操作,更加底层。而过滤器关注的是如何实现业务。