手把手帶你定制.NET?6.0的Middleware中間件
前言
在本文中,我們將學(xué)習(xí)中間件,以及如何使用它進(jìn)一步定制應(yīng)用程序。我們將快速學(xué)習(xí)中間件的基礎(chǔ)知識(shí),然后探討如何使用它做的一些特殊事情。
本文涵蓋的主題包括:
- 中間件簡(jiǎn)介
- 編寫(xiě)自定義中間件
- 中間件的潛力
- 如何使用中間件
本章所處的位置,如下圖所示:

技術(shù)準(zhǔn)備
我們使用控制臺(tái)、shell或Bash終端先創(chuàng)建一個(gè)ASP.NET Core MVC應(yīng)用程序,然后切換到工作目錄:
dotnet new web -n MiddlewaresDemo -o MiddlewaresDemo
然后用VS打開(kāi)項(xiàng)目:
cd MiddlewaresDemo code .
注意在.NET 6.0中,web項(xiàng)目模板發(fā)生了變化。Microsoft引入了minimal API,項(xiàng)目模板默認(rèn)使用minimal API。
中間件簡(jiǎn)介
大多數(shù)人可能已經(jīng)知道中間件是什么,但有些人可能不知道,即使你已經(jīng)在使用ASP.NET Core有一段時(shí)間了。我們一般不需要詳細(xì)了解中間件實(shí)例,因?yàn)樗鼈兇蠖嚯[藏在擴(kuò)展方法后面,例如UseMvc()、UseAuthentication()、UseDeveloperExceptionPage()等。每次在Configure方法中,我們默認(rèn)將隱式地使用至少一個(gè)或更多個(gè)中間件組件。
中間件組件是處理請(qǐng)求管道的一段代碼。我們可以將請(qǐng)求流程想象成一串管道,每次請(qǐng)求調(diào)用,都會(huì)返回一個(gè)響應(yīng)。中間件負(fù)責(zé)創(chuàng)建回聲——它操縱請(qǐng)求上下文,加工處理、疊加邏輯、豐富信息。

中間件組件按配置順序執(zhí)行。配置的第一個(gè)中間件組件是第一個(gè)執(zhí)行的組件。我們可以把中間件看成回旋鏢,出去的時(shí)候第一個(gè)執(zhí)行,回來(lái)的時(shí)候最后一個(gè)執(zhí)行。
在ASP.NET Core web應(yīng)用程序,如果客戶端請(qǐng)求的是圖像或任何其他靜態(tài)文件,StaticFileMiddleware將負(fù)責(zé)查找該資源,如果找到該資源,則返回該資源。如果沒(méi)有,這個(gè)中間件除了調(diào)用下一個(gè)之外什么都不做。
MvcMiddleware組件檢查請(qǐng)求的資源,將其映射到已配置的路由,執(zhí)行控制器,創(chuàng)建視圖,并返回HTML或Web API結(jié)果。如果MvcMiddleware沒(méi)有找到匹配的控制器,它無(wú)論如何都會(huì)返回一個(gè)結(jié)果——通常是一個(gè)404狀態(tài)的結(jié)果,這就是為什么MvcMiddleware是最后配置的中間件。
異常處理中間件通常是配置的第一批的中間件之一,不是因?yàn)樗堑谝粋€(gè)執(zhí)行的,而是因?yàn)樗亲詈笠粋€(gè)執(zhí)行的。異常處理驗(yàn)證結(jié)果,并以客戶端友好的方式在瀏覽器中顯示可能的異常。以下過(guò)程描述了運(yùn)行時(shí)發(fā)生的500錯(cuò)誤狀態(tài):
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
在ASP.NET Core 6.0,Microsoft引入了minimal API,它簡(jiǎn)化了應(yīng)用配置,并隱藏了許多默認(rèn)配置,比如隱式的using聲明,因此,在頭部我們看不到任何using語(yǔ)句,以上就是我們看到的ASP.NET Core 6.0中的Program.cs文件內(nèi)容。
在這里,lambda中間件綁定到默認(rèn)路由,只有一句簡(jiǎn)單的“Hello World!”響應(yīng)流。這個(gè)特殊的中間件會(huì)終止管道并返回響應(yīng)內(nèi)容。因此,它是最后一個(gè)運(yùn)行的中間件。
下面我們把a(bǔ)pp.MapGet()做個(gè)替換,如下所示:
app.Use(async (context, next) =>{
await context.Response.WriteAsync("===");
await next();
await context.Response.WriteAsync("===");
});
app.Use(async (context, next) => {
await context.Response.WriteAsync(">>>>>> ");
await next();
await context.Response.WriteAsync(" <<<<<<");
});
app.Run(async context => {
await context.Response.WriteAsync("Hello World!");
});
這里調(diào)用兩個(gè)app.Use()方法,并且創(chuàng)建了兩個(gè)lambda中間件,除了做簡(jiǎn)單的處理外,中間件還調(diào)用了它們的后繼組件,每個(gè)中間件的調(diào)用鏈很明確很清晰。在調(diào)用下一個(gè)中間件之前,處理實(shí)際的請(qǐng)求,在調(diào)用下個(gè)中間件之后,處理響應(yīng)。以上就是管道的工作機(jī)制。
如果現(xiàn)在運(yùn)行程序(使用dotnet run)并在瀏覽器中打開(kāi)URL,我們應(yīng)該會(huì)看到這樣的純文本結(jié)果
===>>>>>> Hello World! <<<<<<===
不知道您理解了沒(méi)?如果理解了,我們往下學(xué)習(xí),看看如何使用這個(gè)概念向請(qǐng)求管道添加一些附加功能。
編寫(xiě)自定義中間件
中間件可以說(shuō)是ASP.NET Core的基座,在請(qǐng)求期間執(zhí)行的所有邏輯都基于此機(jī)制。因此,我們可以使用它向web添加自定義功能。在下面案例,我們希望找出通過(guò)請(qǐng)求管道的每個(gè)請(qǐng)求的執(zhí)行時(shí)間:
我們可以在調(diào)用下一個(gè)中間件之前創(chuàng)建并啟動(dòng)秒表,然后在調(diào)用下個(gè)中間件之后停止測(cè)量執(zhí)行時(shí)間,如下所示:
app.Use(async (context, next) => {
var s = new Stopwatch();
s.Start();
//其他操作
await next();
s.Stop();
//結(jié)束度量
var result = s.ElapsedMilliseconds;
//統(tǒng)計(jì)耗時(shí)
await context.Response.WriteAsync($"耗時(shí):{result} 秒。");
});
記得為System.Diagnostics添加using語(yǔ)句。
之后,我們將經(jīng)過(guò)的毫秒返回到響應(yīng)流。
如果您編寫(xiě)的中間件組件很多,Program.cs將變得非?;靵y。所以大多數(shù)中間件組件將被編寫(xiě)為獨(dú)立的類,如下所示:
using System.Diagnostics;
public class StopwatchMiddleware { ???
private readonly RequestDelegate _next; ????
public StopwatchMiddleware(RequestDelegate next) ?
{ ?
_next = next; ?
} ????
public async Task Invoke(HttpContext context) { ????????
var s = new Stopwatch(); ????????
s.Start(); ????????
//其他操作 ????????
await _next(context); ????????
s.Stop();
//結(jié)束度量 ????????
var result = s.ElapsedMilliseconds; ????????
//統(tǒng)計(jì)耗時(shí)
await context.Response.WriteAsync($"耗時(shí):{result} 秒。"); ??
}
}
在Invoke方法中的,我們獲得構(gòu)造函數(shù)和當(dāng)前上下文獲得要執(zhí)行的下一個(gè)中間件組件。
注意:
中間件在應(yīng)用程序啟動(dòng)時(shí)初始化,構(gòu)造函數(shù)在應(yīng)用程序生命周期內(nèi)僅運(yùn)行一次。另一方面,每個(gè)請(qǐng)求調(diào)用一次Invoke方法。
要使用此中間件,您可以使用一個(gè)通用的UseMiddleware方法:
app.UseMiddleware<StopwatchMiddleware>();
然而,更優(yōu)雅的方法是創(chuàng)建一個(gè)封裝此調(diào)用的擴(kuò)展方法:
public static class StopwatchMiddlewareExtension {
public static IApplicationBuilder UseStopwatch(this IApplicationBuilder app)
{
app.UseMiddleware<StopwatchMiddleware>();
return app;
}
}
然后就可以這樣使用:
app.UseStopwatch();
這樣,您可以通過(guò)請(qǐng)求管道向ASP.NET Core應(yīng)用程序提供其他功能。中間件中提供了整個(gè)HttpContext。這樣,您可以使用中間件操縱請(qǐng)求和響應(yīng)。
例如,AuthenticationMiddleware嘗試從請(qǐng)求中收集用戶信息。如果找不到任何信息,它將通過(guò)向客戶端發(fā)送特定的響應(yīng)來(lái)請(qǐng)求信息。如果它找到,它會(huì)將其添加到請(qǐng)求上下文中,并以這種方式將其提供給整個(gè)應(yīng)用程序。
中間件的潛力
使用中間件還可以做許多其他事情。例如,可以將請(qǐng)求管道拆分為兩個(gè)或多個(gè)管道,我們將在這里討論如何做到這一點(diǎn)。
使用/map分支管道
下一段代碼顯示了如何基于特定路徑創(chuàng)建請(qǐng)求管道的分支:
app.Map("/map1", app1 => {
// 其他中間件
app1.Run(async context => {
await context.Response.WriteAsync("Map Test 1");
});
});
app.Map("/map2", app2 => {
// 其他中間件
app2.Run(async context => {
await context.Response.WriteAsync("Map Test 2");
});
});
// 其他中間件/map1路徑是一個(gè)特定的分支,它在內(nèi)部繼續(xù)請(qǐng)求管道,/map2與此相同。這兩個(gè)map都有自己內(nèi)部的中間件配置。所有其他未指定的路徑都遵循該主分支。
使用MapWhen分支管道
還有一個(gè)MapWhen方法可以根據(jù)條件分支管道,而不是根據(jù)路徑分支:
public void Configure(IApplicationBuilder app) {
app.MapWhen(context =>context.Request.Query.ContainsKey("分支"),
app1 => {
// 其他中間件
app1.Run(async context => {
await context.Response.WriteAsync( "MapBranch Test");
});
});
//其他中間件
app.Run(async context => {
await context.Response.WriteAsync("Hello non-Map.");
});
}
使用中間件構(gòu)造條件
我們一般可以根據(jù)配置值創(chuàng)建條件,或者根據(jù)請(qǐng)求上下文的屬性創(chuàng)建條件。在前面的示例中,我們使用了查詢字符串屬性作為條件。當(dāng)然,你也可以使用HTTP標(biāo)頭、表單屬性或請(qǐng)求上下文的任何其他屬性。
如果需要,還可以嵌套map以創(chuàng)建子分支和孫分支。
我們?cè)倏聪陆】禉z查中間件,ASP.NET Core HealthCheck API的工作原理如下:
首先,它使用MapWhen指定要使用的端口,然后,它使用Map設(shè)置HealthCheck API路徑(如果未指定端口則使用Map)。最后,使用了HealthCheckMiddleware。我們看下面的代碼示例:
private static void UseHealthChecksCore(IApplicationBuilder app, PathString path, int? port, object[] args)
{
if (port == null)
{
app.Map(path, b => b.UseMiddleware<HealthCheckMiddleware>(args));
}
else {
app.MapWhen(c => c.Connection.LocalPort == port,
b0 => b0.Map(path, b1 =>b1.UseMiddleware<HealthCheckMiddleware>(args)));
};
}這里,我們可以使用Map或MapWhen分別基于特定路徑或特定條件提供特殊的API或資源。
接下來(lái),讓我們看看如何在更新版本的ASP.NET Core中使用終止中間件組件。
在ASP.NET Core 3.0及更高版本中使用中間件
在ASP.NET Core 3.0及更高版本,有兩種新的中間件,它們被稱為UseRouting和UseEndpoints:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.MapGet("/", async context => {
await context.Response.WriteAsync("Hello World!");
});
});
}
第一個(gè)是使用路由的中間件UseRouting,另一個(gè)是訪問(wèn)地址的UseEndpoints。
這是新的端點(diǎn)路由。以前,路由是MVC的一部分,它只適用于MVC、Web API和基于MVC的框架。然而在ASP.NET Core 3.0及更高版本,路由不再是MVC框架中的一部分?,F(xiàn)在,MVC和其他框架都可以被映射到特定的路由或端點(diǎn)。
在前面的代碼段中,GET請(qǐng)求被映射到頁(yè)面根URL。在下一個(gè)代碼片段中,MVC被映射到路由模式,RazorPages被映射到基于RazorPage的特定文件結(jié)構(gòu)的路由:
app.UseEndpoints(endpoints => {
endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
現(xiàn)在已經(jīng)沒(méi)有UseMvc方法了,即使它仍然存在并在IApplicationBuilder對(duì)象級(jí)別上工作,以防止現(xiàn)有代碼中斷?,F(xiàn)在,激活A(yù)SP.NET Core功能的方法更為精細(xì)。
- Areas for MVC and web API:
endpoints.MapAreaControllerRoute(...); - MVC and web API:
endpoints.MapControllerRoute(...); - Blazor server-side:
endpoints.MapBlazorHub(...); - SignalR:
endpoints.MapHub(...); - Razor Pages:
endpoints.MapRazorPages(...); - Health checks:
endpoints.MapHealthChecks(...);
這些是ASP最常用的新Map方法。
還有很多方法可以定義回退地址,比如將路由和HTTP方法映射到代理,以及中間件組件。
你可以創(chuàng)建適用于所有請(qǐng)求的中間件,例如StopWatchMiddleware,你也可以編寫(xiě)中間件以在特定路徑或路由上工作,例如創(chuàng)建一個(gè)Map方法,以將其映射到該路由。
注意事項(xiàng)
不再建議在中間件內(nèi)部處理路由。相反,您應(yīng)該使用新的地址路由。使用這種方法,中間件更加通用,它可以通過(guò)單一的配置就可以在多個(gè)路由上工作。
重寫(xiě)終止中間件
接下來(lái),我們創(chuàng)建小型虛擬中間件,將應(yīng)用程序狀態(tài)寫(xiě)入特定路由。在此示例中,沒(méi)有自定義路由處理:
namespace MiddlewaresSample;
public class AppStatusMiddleware {
private readonly RequestDelegate _next;
private readonly string _status;
public AppStatusMiddleware(RequestDelegate next, string status)
{
_next = next;
_status = status;
}
public async Task Invoke(HttpContext context) {
await context.Response.WriteAsync($"Hello {_status}!");
}
}
我們需要做的是在IEndpointRouteBuilder對(duì)象上編寫(xiě)一個(gè)擴(kuò)展方法。此方法將路由模式作為可選參數(shù),并返回IEndpointConventionBuilder對(duì)象以啟用跨域資源共享(CORS)、身份驗(yàn)證或路由的其他條件。
現(xiàn)在,我們應(yīng)該添加一個(gè)擴(kuò)展方法,以便更容易地使用中間件:
public static class MapAppStatusMiddlewareExtension {
public static IEndpointConventionBuilder MapAppStatus(this IEndpointRouteBuilder routes, string pattern = "/", string name = "World")
{
var pipeline = routes.CreateApplicationBuilder().UseMiddleware<AppStatusMiddleware>(name).Build();
return routes.Map(pattern, pipeline).WithDisplayName("AppStatusMiddleware");
}
}
完成后,我們可以使用MapAppStatus方法將其映射到特定路線:
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.MapGet("/", () => "Hello World!");
endpoints.MapAppStatus("/status", "Status");
});
現(xiàn)在,我們可以通過(guò)輸入以下地址在瀏覽器中調(diào)用路由: http://localhost:5000/status
總結(jié)
大多數(shù)ASP.NET Core功能基于中間件,在本章中,我們學(xué)習(xí)了中間件的工作原理以及如何創(chuàng)建自己的中間件組件來(lái)擴(kuò)展ASP.NET框架。我們還學(xué)習(xí)了如何使用新路由向自定義的終止中間件添加路由。
在下一章中,我們將了解ASP.NET Core中的新端點(diǎn)路由,它允許我們以簡(jiǎn)單靈活的方式創(chuàng)建自己的托管端點(diǎn)。
到此這篇關(guān)于定制.NET 6.0的Middleware中間件的文章就介紹到這了,更多相關(guān)定制.NET 6.0 Middleware中間件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文輕松了解ASP.NET與ASP.NET?Core多環(huán)境配置對(duì)比
ASP.NET?Core支持在多個(gè)環(huán)境中管理應(yīng)用程序,如開(kāi)發(fā)(Development),預(yù)演(Staging)和生產(chǎn)(Production),下面這篇文章主要給大家介紹了關(guān)于ASP.NET與ASP.NET?Core多環(huán)境配置對(duì)比?的相關(guān)資料,需要的朋友可以參考下2022-04-04
asp.net顯示圖片到指定的Image控件中 具體實(shí)現(xiàn)
這篇文章介紹了asp.net顯示圖片到指定的Image控件中 具體實(shí)現(xiàn),有需要的朋友可以參考一下2013-11-11
asp.net計(jì)算每個(gè)頁(yè)面執(zhí)行時(shí)間的方法
這篇文章主要介紹了asp.net計(jì)算每個(gè)頁(yè)面執(zhí)行時(shí)間的方法,涉及asp.net操作時(shí)間的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
ASP.NET動(dòng)態(tài)增加HTML元素的方法實(shí)例小結(jié)
這篇文章主要介紹了ASP.NET動(dòng)態(tài)增加HTML元素的方法,結(jié)合實(shí)例形式總結(jié)分析了asp.net針對(duì)樣式、Meta、js等元素動(dòng)態(tài)增加相關(guān)操作技巧,需要的朋友可以參考下2017-01-01
asp.net GridView排序簡(jiǎn)單實(shí)現(xiàn)
使用javascript操作table排序才是實(shí)用的排序,這樣排序不怎么好,但是有時(shí)候可能會(huì)用來(lái),記錄一下。2009-12-12
jQuery調(diào)用WebService返回JSON數(shù)據(jù)及參數(shù)設(shè)置注意問(wèn)題
.NET Framework 3.5的發(fā)布解決了WebService調(diào)用中json問(wèn)題,本文將介紹jQuery調(diào)用基于.NET Framework 3.5的WebService返回JSON數(shù)據(jù),感興趣的朋友可以了解下,希望本文對(duì)你有所幫助2013-01-01
ASP.NET清空緩存時(shí)遇到的問(wèn)題簡(jiǎn)析
本文將為大家介紹的是ASP.NET網(wǎng)站清空緩存時(shí)遇到的問(wèn)題,主要是基于ObjectDataSource讀取數(shù)據(jù)位置的問(wèn)題,希望對(duì)大家有所幫助。2015-10-10

