聊聊 ASP.NET Core 中間件和過濾器的區(qū)別解析
引言
不知道你有沒有在面試中遇到過這樣的問題:"中間件和過濾器的區(qū)別是什么?",或者在平時(shí)開發(fā)中思考過:"一個(gè)請(qǐng)求進(jìn)來,ASP.NET Core 到底是怎么一步步處理它的?"
這篇文章就來聊聊,不會(huì)涉及太深的源碼,主要面向初級(jí)開發(fā)者,幫你建立一個(gè)清晰的認(rèn)知。
先說中間件
中間件是 ASP.NET Core 處理 HTTP 請(qǐng)求的基本部件,是框架本身的組成部分。
每一個(gè)請(qǐng)求進(jìn)來后,按照 Use 的注冊(cè)順序,依次經(jīng)過每一個(gè)中間件,到達(dá)終端后,再按照相反的方向依次返回。一系列中間件串聯(lián)起來,就組成了 ASP.NET Core 的請(qǐng)求管道。這個(gè)模型有人叫"洋蔥模型",也有人叫"俄羅斯套娃模型",都是一個(gè)意思。
請(qǐng)求 → 中間件A → 中間件B → 中間件C(終端) 響應(yīng) ← 中間件A ← 中間件B ←
中間件可以做什么呢?它收到請(qǐng)求后,可以在請(qǐng)求/響應(yīng)對(duì)象上加點(diǎn)佐料(自己的邏輯),然后調(diào)用 next() 把請(qǐng)求傳遞給下一個(gè)中間件。如果不調(diào)用 next(),請(qǐng)求就在這里短路了,后面的中間件不會(huì)執(zhí)行,這個(gè)中間件就成了終端中間件。
下面是一個(gè)簡單示例,演示如何自定義一個(gè)記錄請(qǐng)求耗時(shí)的中間件:
// 定義中間件類
public class TimingMiddleware
{
private readonly RequestDelegate _next;
public TimingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var sw = Stopwatch.StartNew();
// 調(diào)用 next(),請(qǐng)求繼續(xù)往下走
await _next(context);
// next() 返回后,響應(yīng)正在往回走
sw.Stop();
Console.WriteLine($"[{context.Request.Path}] 耗時(shí):{sw.ElapsedMilliseconds}ms");
}
}
// 在 Program.cs 中注冊(cè)
var app = builder.Build();
app.UseMiddleware<TimingMiddleware>(); // 注冊(cè)自定義中間件
app.UseRouting();
app.UseAuthorization();
app.MapControllers();
app.Run();
如果你不想單獨(dú)定義一個(gè)類,也可以用 app.Use 直接寫一個(gè)內(nèi)聯(lián)中間件:
app.Use(async (context, next) =>
{
Console.WriteLine($"請(qǐng)求進(jìn)來了:{context.Request.Path}");
await next(); // 傳遞給下一個(gè)中間件
Console.WriteLine($"響應(yīng)回來了:{context.Response.StatusCode}");
});
這里注冊(cè)的是終端中間件,不接受 next,請(qǐng)求到這里就結(jié)束了,不會(huì)再往下傳:
app.Run(async context =>
{
await context.Response.WriteAsync("到終點(diǎn)了,不繼續(xù)往下走");
});
再聊過濾器
過濾器是在路由中間件內(nèi)部起作用的。
請(qǐng)求經(jīng)過中間件管道,到達(dá)路由中間件后,路由中間件確定了要調(diào)用哪個(gè) Controller / Action。在真正執(zhí)行 Action 的前后,過濾器就夾在這里介入。
用一張簡單的示意圖來表示:
請(qǐng)求進(jìn)來
→ 中間件們
→ 路由中間件(確定目標(biāo) Action)
→ 過濾器們(Action 執(zhí)行前后插手)
→ Action 執(zhí)行
← 過濾器們
← 路由中間件
← 中間件們
響應(yīng)返回
正因?yàn)檫^濾器處于路由之后,所以它能拿到 MVC 的上下文信息,比如當(dāng)前是哪個(gè) Action、Action 上有哪些 Attribute、ModelState 是否合法等等。這是普通中間件做不到的,因?yàn)橹虚g件執(zhí)行的時(shí)候,路由還沒解析呢。
// 定義過濾器類,繼承 ActionFilterAttribute
public class ValidateModelAttribute : ActionFilterAttribute
{
// 在 Action 執(zhí)行之前觸發(fā)
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
// ModelState 校驗(yàn)不通過,直接短路,返回 400
var errors = context.ModelState
.Where(e => e.Value?.Errors.Count > 0)
.ToDictionary(
e => e.Key,
e => e.Value!.Errors.Select(x => x.ErrorMessage).ToArray()
);
context.Result = new BadRequestObjectResult(new
{
code = 400,
message = "參數(shù)校驗(yàn)失敗",
errors
});
}
}
// 在 Action 執(zhí)行之后觸發(fā)
public override void OnActionExecuted(ActionExecutedContext context)
{
// 可以在這里統(tǒng)一處理響應(yīng),比如包一層統(tǒng)一的返回格式
}
}
使用方式很簡單,直接作為 Attribute 標(biāo)注在 Controller 或 Action 上:
// 標(biāo)注在單個(gè) Action 上
[HttpPost]
[ValidateModel]
public IActionResult CreateUser([FromBody] CreateUserRequest request)
{
// 走到這里說明 ModelState 已經(jīng)通過校驗(yàn)
return Ok(new { message = "創(chuàng)建成功" });
}
// 也可以標(biāo)注在 Controller 上,對(duì)所有 Action 生效
[ApiController]
[ValidateModel]
public class UserController : ControllerBase
{
// ...
}
如果想全局生效,在 Program.cs 里注冊(cè)就行:
builder.Services.AddControllers(options =>
{
options.Filters.Add<ValidateModelAttribute>(); // 全局注冊(cè)
});
它倆的區(qū)別
| 中間件 | 過濾器 | |
|---|---|---|
| 作用范圍 | 所有 HTTP 請(qǐng)求,包括靜態(tài)文件 | 僅限 Controller / Action |
| 典型使用場(chǎng)景 | 限流、壓縮、跨域、日志、認(rèn)證 | 權(quán)限校驗(yàn)、參數(shù)校驗(yàn)、統(tǒng)一響應(yīng)封裝、異常處理 |
一句話總結(jié):中間件是 HTTP 管道的基礎(chǔ)設(shè)施,過濾器是 MVC 系的切面機(jī)制,它們不是同一層次的東西。
給你一個(gè)小技巧,不確定用哪個(gè)的時(shí)候,問自己一個(gè)問題:我需要知道當(dāng)前是哪個(gè) Action 嗎? 需要的話用過濾器,不需要的話中間件就夠了。
內(nèi)置清單
最后整理一份 ASP.NET Core 已經(jīng)提供的常用中間件和過濾器,遇到需求的時(shí)候先查查有沒有現(xiàn)成的,別重復(fù)造輪子。
常用內(nèi)置中間件:
| 中間件 | 說明 |
|---|---|
UseRouting | 路由解析 |
UseAuthentication | 認(rèn)證 |
UseAuthorization | 授權(quán) |
UseStaticFiles | 靜態(tài)文件服務(wù) |
UseCors | 跨域 |
UseExceptionHandler | 全局異常處理 |
UseHttpsRedirection | HTTPS 重定向 |
UseResponseCompression | 響應(yīng)壓縮 |
UseRateLimiter | 限流(.NET 7 新增) |
內(nèi)置過濾器:
| 過濾器 | 說明 |
|---|---|
[Authorize] | 授權(quán)校驗(yàn) |
[AllowAnonymous] | 允許匿名訪問 |
[RequireHttps] | 強(qiáng)制 HTTPS |
[ValidateAntiForgeryToken] | 防止 CSRF 攻擊 |
[ResponseCache] | 響應(yīng)緩存 |
[Produces] | 指定響應(yīng)內(nèi)容類型 |
[Consumes] | 指定請(qǐng)求內(nèi)容類型 |
到此這篇關(guān)于聊聊 ASP.NET Core 中間件和過濾器的區(qū)別解析的文章就介紹到這了,更多相關(guān)asp.net core中間件和過濾器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
.NET6環(huán)境下實(shí)現(xiàn)MQTT通信及詳細(xì)代碼演示
本文詳細(xì)講解了.NET6環(huán)境下實(shí)現(xiàn)MQTT通信的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
asp.net StringBuilder的用法 實(shí)例代碼
StringBuilder實(shí)現(xiàn)的帶數(shù)字的排行榜效果實(shí)現(xiàn)代碼。2009-07-07
.NET Core中創(chuàng)建和使用NuGet包的示例代碼
這篇文章主要介紹了.NET Core中創(chuàng)建和使用NuGet包的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
ASP.net基礎(chǔ)知識(shí)之常見錯(cuò)誤分析
ASP.net基礎(chǔ)知識(shí)之常見錯(cuò)誤分析...2007-07-07
運(yùn)用.net core中實(shí)例講解RabbitMQ高可用集群構(gòu)建
這篇文章主要介紹了運(yùn)用.net core中實(shí)例講解RabbitMQ高可用集群構(gòu)建,文中相關(guān)示例代碼講解的非常清晰,感興趣的小伙伴可以參考一下這篇文章,相信可以幫助到你2021-09-09
asp.net(C#)把漢字轉(zhuǎn)化成全拼音函數(shù)(全拼)
asp.net(C#)把漢字轉(zhuǎn)化成全拼音函數(shù)的代碼,需要的朋友可以參考下。2009-12-12
asp.net中生成縮略圖并添加版權(quán)實(shí)例代碼
這篇文章介紹了asp.net中生成縮略圖并添加版權(quán)實(shí)例代碼,有需要的朋友可以參考一下2013-11-11
淺談從ASP.NET Core2.2到3.0你可能會(huì)遇到這些問題
這篇文章主要介紹了ASP.NET Core2.2到3.0可能會(huì)遇到的問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04

