ASP.NET?Core自定義中間件的方式詳解
ASP.NET Core應(yīng)用本質(zhì)上,其實(shí)就是由若干個(gè)中間件構(gòu)建成的請(qǐng)求處理管道。管道相當(dāng)于一個(gè)故事的框架,而中間件就相當(dāng)于故事中的某些情節(jié)。同一個(gè)故事框架采用不同的情節(jié)拼湊,最終會(huì)體現(xiàn)出不同風(fēng)格的故事。而我們的ASP.NET Core應(yīng)用也正是如此,同一管道采用不同的中間件組合,最終也會(huì)呈現(xiàn)出不同的應(yīng)用形態(tài)。
從上述的概念種可以看出,中間件在ASP.NET Core應(yīng)用有著舉足輕重的地位。雖然ASP.NET Core為我們提供了一組豐富的內(nèi)置中間件,但有些時(shí)候我們可能會(huì)需要自定義一些中間件,將其穿插到管道中,以便滿足我們特定業(yè)務(wù)場(chǎng)景的需求,所以本文將介紹3種方式來滿足自定義中間件的需求。
1.委托形式
在應(yīng)用程序代碼中,我們可以從用于注冊(cè)中間件的Use方法中看出,所謂管道中的中間件其實(shí)就是一種委托類型的對(duì)象,這個(gè)具體的委托對(duì)象體現(xiàn)為“Fun<RequestDelegate,RequestDelegate>”。
從Fun<RequestDelegate,RequestDelegate>委托的定義可以看出,該委托類型的入?yún)⒑头祷刂刀际且粋€(gè)RequestDelegate委托類型的對(duì)象。RequestDelegate委托類型其實(shí)就是管道在代碼中的體現(xiàn)形式,該委托類型承載很多關(guān)于請(qǐng)求響應(yīng)的重要信息,定義如下:
public delegate Task RequestDelegate(HttpContext context);
Fun<RequestDelegate,RequestDelegate>委托中,入?yún)⒌腞equestDelegate對(duì)象表示由上一個(gè)中間件構(gòu)建的管道,返回值的RequestDelegate對(duì)象表示:將當(dāng)前中間件基于上一個(gè)管道處理后生成的新管道。由于中間件體現(xiàn)為一個(gè)Fun<RequestDelegate,RequestDelegate>委托對(duì)象,那么這就代表我們可以定義一個(gè)與該委托具有一致聲明的方法作為自定義中間件的方式。具體的代碼實(shí)現(xiàn)方式如下:
//創(chuàng)建應(yīng)用 var app = WebApplication.Create(args); //轉(zhuǎn)換獲得應(yīng)用建造者 IApplicationBuilder appBuilder = app; //注冊(cè)自定義的中間件 appBuilder.Use(SayHi); //運(yùn)行應(yīng)用 app.Run(); //定義為Fun<RequestDelegate,RequestDelegate>類型的方法 static RequestDelegate SayHi(RequestDelegate request) => httpContext => httpContext.Response.WriteAsync("Hello");
上面的代碼是在一個(gè)原始的控制臺(tái)程序中編寫的,并且自行進(jìn)行了主機(jī)應(yīng)用的構(gòu)建。在代碼中定義了一個(gè)和Fun<RequestDelegate,RequestDelegate>委托簽名一致的SayHi方法,并以此方法作為中間件進(jìn)行了引用。雖然這是一個(gè)可行的方式,但在實(shí)際開發(fā)的工作場(chǎng)景中,其實(shí)很少會(huì)使用委托形式作為自定義中間件的方式。在此處之所以演示這種形式,主要是為了表面中間件本質(zhì)是一個(gè)委托,并且不管通過什么形式去定義中間件,它最終都會(huì)體現(xiàn)為一個(gè)Fun<RequestDelegate,RequestDelegate>委托對(duì)象。
2.強(qiáng)類型中間件
在實(shí)際的開發(fā)過程中,基本上都會(huì)將自定義的中間件定義為一個(gè)具體類型,而對(duì)于使用強(qiáng)類型的中間件而言,則我們定義的中間件類型必須實(shí)現(xiàn)IMiddleware接口。既然通過一個(gè)具體類型來定義中間件,類型在使用上則勢(shì)必會(huì)與其他類型產(chǎn)生依賴關(guān)聯(lián)性,那么對(duì)于中間件類型中依賴服務(wù)的實(shí)例化,框架則要求我們使用依賴注入的方式。接下來我們將通過代碼示例演示如何定義一個(gè)強(qiáng)類型的中間件。
2.1.定義中間件的依賴
下面代碼定義的類型是我們預(yù)先為中間件類型定義的依賴項(xiàng),ISeasonTips接口類型的作用主要是,根據(jù)不同月份獲取對(duì)應(yīng)的季節(jié),并輸出對(duì)應(yīng)季節(jié)的注意事項(xiàng),其中SeasonTips類型是接口的默認(rèn)實(shí)現(xiàn)。
public interface ISeasonTips { string Prompt(DateTimeOffset time); } public class SeasonTips : ISeasonTips { //根據(jù)不同月份提示季節(jié)注意事項(xiàng) public string Prompt(DateTimeOffset time) => time.Month switch { var h when h >= 3 && h <= 5 => "春天到了,早晚溫差比較大,要注意別感冒。", var h when h >= 6 && h <= 8 => "夏天到了,天氣炎熱,要注意別防嗮。", var h when h >= 9 && h <= 11 => "秋天到了,天氣干燥,要注意多喝水。", _ => "冬天到了,天氣寒冷,要注意防寒保暖。" }; //END Prompt() }
2.2.定義中間件類型
下面的代碼中,我們定義了一個(gè)名為SeasonMiddleware的中間件類型,并實(shí)現(xiàn)IMiddleware接口。該中間件的處理請(qǐng)求的邏輯在InvokeAsync方法中,該方法調(diào)用其依賴類型的Prompt方法,根據(jù)當(dāng)前時(shí)間獲取當(dāng)前季節(jié)的注意事項(xiàng)進(jìn)行輸出。在該調(diào)用該方法后,我們還對(duì)InvokeAsync的另一個(gè)參數(shù):“RequestDelegate類型的委托對(duì)象”進(jìn)行了調(diào)用,以便執(zhí)行管道中的下一個(gè)中間件。另外,對(duì)于中間件依賴的類型ISeasonTips,我們將其定義在構(gòu)造函數(shù)的參數(shù)列表上,以便依賴注入容器提供相應(yīng)的實(shí)例。
/// <summary> /// 強(qiáng)類型中間件 /// </summary> public class SeasonMiddleware : IMiddleware { //依賴類型,通過構(gòu)造函數(shù)進(jìn)行依賴注入 private readonly ISeasonTips _seasonTips; public SeasonMiddleware(ISeasonTips seasonTips) { _seasonTips = seasonTips; } //調(diào)用依賴的“季節(jié)提示類型”,根據(jù)當(dāng)前時(shí)間獲取當(dāng)前季節(jié)的注意事項(xiàng),并進(jìn)行響應(yīng)輸出 public async Task InvokeAsync(HttpContext context, RequestDelegate next) { await context.Response.WriteAsync(_seasonTips.Prompt(DateTimeOffset.Now)); //調(diào)用管道中的下一個(gè)中間件 await next(context); } // END InvokeAsync() } // END Class
在下面的代碼中我們對(duì)自定義的“強(qiáng)類型中間件”進(jìn)行了應(yīng)用。由于“強(qiáng)類型中間件”的實(shí)例以及依賴都是由依賴注入容器提供的,所以不僅要對(duì)依賴的服務(wù)進(jìn)行注冊(cè),還要對(duì)自身的中間件類型進(jìn)行服務(wù)注冊(cè)。在服務(wù)注冊(cè)之后,我們使用WebApplication對(duì)象的UseMiddleware<SeasonMiddleware>擴(kuò)展方法,將該中間件添加到應(yīng)用程序的請(qǐng)求管道中。由于在該中間件后沒有其他中間件的處理,所以我們通過調(diào)用Run擴(kuò)展方法注冊(cè)了管道末端的中間件,以便結(jié)束當(dāng)前請(qǐng)求,將響應(yīng)輸出到客戶端。
using dotNet6Demo; //創(chuàng)建“應(yīng)用建造者” var builder = WebApplication.CreateBuilder(args); //服務(wù)注冊(cè) builder.Services.AddSingleton<ISeasonTips, SeasonTips>().AddSingleton<SeasonMiddleware>(); //構(gòu)建應(yīng)用 var app = builder.Build(); //引用強(qiáng)類型中間件 app.UseMiddleware<SeasonMiddleware>(); //末端的中間件 app.Run(async (context) => { await context.Response.WriteAsync("請(qǐng)求結(jié)束"); }); //運(yùn)行應(yīng)用 app.Run();
到目前為止,結(jié)合本示例以上的3個(gè)步驟,啟動(dòng)運(yùn)行程序就可以驗(yàn)證自定義強(qiáng)類型中間件的效果了。
3.基于約定的中間件
對(duì)于ASP.NET的開發(fā)者而言,基于約定的編程模式應(yīng)該不會(huì)陌生。例如在ASP.NET MVC框架中,“Action”默認(rèn)查找視圖就有一種基于約定的規(guī)則,即“Action”首先會(huì)在Views目錄中查找與當(dāng)前“Controller”同名的目錄,然后在該目錄中查找與“Action”同名的視圖文件。這種基于約定的設(shè)計(jì)方式,在自定義中間件領(lǐng)域也同樣使用到了,即基于約定的中間件。
3.1.約定規(guī)則
基于約定的中間件它不必像強(qiáng)類型中間件那樣,必須實(shí)現(xiàn)IMiddleware接口或繼承某些基類,它只用按照框架約定的方式定義中間件類型即可,具體的約定規(guī)則如下:
- 中間件類型必須要定義為一個(gè)公共的、可供外界實(shí)例化的類型,靜態(tài)類型無效;
- 構(gòu)造函數(shù)的參數(shù)中必須包含RequestDelegate類型,如果存在依賴類型則也必須包含在構(gòu)造函數(shù)中;
必須定義InvokeAsync或Invoke方法,方法簽名為:public Task Invoke(HttpContext context);
對(duì)以上的約定進(jìn)行一個(gè)補(bǔ)充說明:構(gòu)造函數(shù)的參數(shù)列表要包含依賴的類型,是為了依賴注入容器對(duì)依賴類型提供實(shí)例;RequestDelegate參數(shù)具有傳遞性,表示由后續(xù)中間件構(gòu)建的管道,當(dāng)前中間件利用它將請(qǐng)求轉(zhuǎn)交給后續(xù)管道進(jìn)行處理。InvokeAsync或Invoke方法主要是代表中間件在管道中處理請(qǐng)求的邏輯。
3.2.應(yīng)用實(shí)現(xiàn)
下面我們?cè)?ldquo;強(qiáng)類型中間件”示例的基礎(chǔ)上,根據(jù)約定規(guī)則將SeasonMiddleware類型改造為“基于約定的中間件”,代碼如下:
/// <summary> /// 基于約定的中間件 /// </summary> public class SeasonMiddleware { private readonly ISeasonTips _seasonTips; private readonly RequestDelegate _next; public SeasonMiddleware(ISeasonTips seasonTips, RequestDelegate next) { _seasonTips = seasonTips; _next = next; } //調(diào)用依賴的“季節(jié)提示類型”,根據(jù)當(dāng)前時(shí)間獲取當(dāng)前季節(jié)的注意事項(xiàng),并進(jìn)行響應(yīng)輸出 public async Task InvokeAsync(HttpContext context) { await context.Response.WriteAsync(_seasonTips.Prompt(DateTimeOffset.Now)); //調(diào)用管道中的下一個(gè)中間件 await _next(context); } // END InvokeAsync() } // END Class
在中間件引用方面,“基于約定的中間件”同樣可以使用“app.UseMiddleware<SeasonMiddleware>()”的方式進(jìn)行引用,但是在此我們介紹一種較為常用的方式,就是將自定義中間件的引用方式進(jìn)行封裝,將其作為IApplicationBuilder類型的擴(kuò)展方法來使用,擴(kuò)展方法定義的代碼如下:
public static class SeasonMiddlewareExtensions { public static IApplicationBuilder UseSeason(this IApplicationBuilder builder) { return builder.UseMiddleware<SeasonMiddleware>(); } }
接下來在示例應(yīng)用方面,將其調(diào)整為使用“基于約定中間件”的形式,并使用擴(kuò)展方法引用中間件。
using dotNet6Demo; //創(chuàng)建“應(yīng)用建造者” var builder = WebApplication.CreateBuilder(args); //服務(wù)注冊(cè) builder.Services.AddSingleton<ISeasonTips, SeasonTips>(); //構(gòu)建應(yīng)用 var app = builder.Build(); //通過自定義擴(kuò)展方法 引用中間件 app.UseSeason(); //末端的中間件 app.Run(async (context) => { await context.Response.WriteAsync("請(qǐng)求結(jié)束"); }); //運(yùn)行應(yīng)用 app.Run();
在對(duì)以上中間件應(yīng)用方面,我們能可以看出“基于約定的中間件”類型并沒有進(jìn)行服務(wù)注冊(cè),而“強(qiáng)類型中間件”類型卻進(jìn)行了服務(wù)注冊(cè),這是因?yàn)閮烧咴谔峁?shí)例的方式上有著本質(zhì)的區(qū)別。
“基于約定的中間件”的實(shí)例是在應(yīng)用啟動(dòng)時(shí)便可提供的,并且只能指定的一個(gè)固定的生命周期模式“Singleton”,所以該類型中間件具有和應(yīng)用程序一樣的生存期,直到應(yīng)用程序關(guān)閉才會(huì)釋放。
“強(qiáng)類型中間件”的實(shí)例并不是在應(yīng)用啟動(dòng)時(shí)提供的,它需要根據(jù)服務(wù)注冊(cè)時(shí)指定的生命周期,來決定創(chuàng)建提供的時(shí)機(jī)。例如“強(qiáng)類型中間件”注冊(cè)的生命周期為“Scoped”,那么依賴注入容器會(huì)根據(jù)客戶端的請(qǐng)求實(shí)時(shí)創(chuàng)建中間件的實(shí)例,請(qǐng)求處理完成后才會(huì)被釋放。
總結(jié)
中間件的使用地位在ASP.NET Core中絕對(duì)是毋庸置疑的,那么對(duì)于較為復(fù)雜的項(xiàng)目而言,自定義中間件的需求絕對(duì)是“繞不開的彎”,所以我們必須掌握自定義中間件的方式。
本文介紹了3種可以實(shí)現(xiàn)自定義ASP.NET Core中間件的方式。其中第一種并不推崇作為實(shí)戰(zhàn)運(yùn)用的手段,其目的是為了讓我們明白:中間件最終的體現(xiàn)形式其實(shí)就是一個(gè)委托對(duì)象,該委托對(duì)象承載了請(qǐng)求上下信息,并具有傳遞性。在實(shí)際的使用中,我們可以在第二種和第三種中進(jìn)行選擇,也就是“強(qiáng)類型中間件”和“基于約定的中間件”,從兩者的特點(diǎn)上來看,“基于約定的中間件”在使用方面會(huì)更加的方便,但是其生命周期模式只能局限于Singleton。而“強(qiáng)類型中間件”可以通過服務(wù)注冊(cè)為中間件實(shí)例指定任意的生命周期模式,相比更加靈活。
對(duì)于具體的選擇,我們想我們還是交給我們實(shí)際的運(yùn)用場(chǎng)景。
如果想了解更多關(guān)于自定義 ASP.NET Core 中間件的方式,可以訪問如下的官方文檔:
寫入自定義 ASP.NET Core 中間件 | Microsoft Docs
到此這篇關(guān)于ASP.NET Core自定義中間件的方式的文章就介紹到這了,更多相關(guān)ASP.NET Core自定義中間件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
把ASP.NET MVC項(xiàng)目部署到本地IIS上的完整步驟
最近會(huì)經(jīng)常修改一些網(wǎng)站前端的內(nèi)容,為了方便跟UI和產(chǎn)品交流,需要將自己修改過的頁面及時(shí)發(fā)布到測(cè)試機(jī)或者是本地的IIS上。下面這篇文章主要給大家介紹了關(guān)于如何把ASP.NET MVC項(xiàng)目部署到本地IIS上的相關(guān)資料,需要的朋友可以參考下2018-06-06HttpRequest Get和Post調(diào)用其他頁面的方法
HttpRequest Get和Post調(diào)用其他頁面的方法,需要的朋友可以參考一下2013-03-03WinForm中窗體間的數(shù)據(jù)傳遞交互的一些方法
通過子窗口向外引發(fā)一個(gè)事件,父窗口去實(shí)現(xiàn)該事件,我們可以再不關(guān)閉父窗口和子窗口的情況下進(jìn)行數(shù)據(jù)的傳輸顯示2012-12-12asp.net 通過UserAgent判斷智能設(shè)備(Android,IOS)
搜集了比較全的 智能設(shè)備 的 Agent,然后又寫了程序,需要的朋友可以參考下2011-10-10.NET某消防物聯(lián)網(wǎng)后臺(tái)服務(wù)內(nèi)存泄漏分析
這篇文章主要為大家介紹了.NET某消防物聯(lián)網(wǎng)后臺(tái)服務(wù)內(nèi)存泄漏分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06