最近博客更新頻率慢了些,原因有三:
其一,最近老周每星期六都錄 ASP.NET Core 的直播,有些內容在視頻里講過,就不太想在博客里面重復。有興趣的話可以去老周的微博看,或者去一直播,直播帳號與微博帳號是綁定的;
其二,最近老周是有些忙,但不是忙寫代碼的事情。而是忙着“尋寶藏”。
其三,每個星期至少也要抽出一天的時間,跟妹子出去浪。准確地說,應該叫對象(Object),是通過構造函數認識的,已經順利運行有五個月了。目前狀態良好,內存占用小,不燒 CPU。性能好,不吃硬件。
好了,屁話不多說了。今天說說如何向中間件傳參數的事。
首先,用宇宙中最通俗的語言介紹一下啥是中間件。瀏覽器或客戶端向服務器發出請求后,HTTP 請求會進入通信管道,然后由一個個中間件來進行處理,A 處理完了,交給 B;B 處理完了再交給 C……所有中間件就串成一條鏈,直到最后一個中間件(HTTP 404)。處理完后就把結果返回給調用者。
所以說,中間件就是一個處理 HTTP 請求的“零件”,比如,你可以定義一個中間件,對請求中的某些數據進行解密或者驗證。或者,你可以定義一個中間件來添加自己定義的 HTTP 頭。
中間件在代碼中由一個 RequestDelegate 委托來表示,該委托聲明如下。
delegate System.Threading.Tasks.Task RequestDelegate(Microsoft.AspNetCore.Http.HttpContext context)
HttpContext 可以保持整個 HTTP 請求上下文中的數據與狀態,並且在調用每一個中間件時,會將自身傳遞過去。返回類型為 Task,表示支持異步等待。
對於代碼簡單的中間件,可以直接 Use 擴展方法,比如這樣。
app.Use(async (context, next) => { context.Response.Headers.Add("key", "no key"); await next(); });
next 是指處理鏈條上的下一個中間件,如果沒特殊事情,在當前代碼處理完后應該調用下一個中間件。當然,如果沒有必要調用下一個中間件,那可以不調用 next,這樣,整個 HTTP 通信管道就中止了。
但是,如果中間件邏輯多,代碼多,而且又要接收參數的話,那就應該用一個類來封裝。封裝的時候一定要注意相關的約定。
中間件必須包含一個 Invoke 或 InvokeAsync 方法,方法的參數為 HttpContext,返回類型為 Task。
為什么要這個約定呢?你看看剛剛那個 RequestDelegate 委托。委托類型的實例是不是有個 Invoke 方法?這就對了,大概為了方便記憶,所以代碼約定也使用了 Invoke 這名字。在運行的時候,框架會在中間件類中尋找名字為 Invoke 或 InvokeAsync 的方法。所以,要想自定義的中間件類起作用,你應該遵守這個約定。
我們今天討論的重點是向中間件傳參數,要能傳參的話,就必須寫一個中間件類。先說說,怎么傳參。方法有二。
一、IApplicationBuilder 的 UseMiddleware 擴展方法,此方法的參數列表中,最后一個是加了 params 關鍵字的 object 數組。這個就是用來傳參數的,而且參數的個數是不確定的。傳入的參數從中間件類的構造函數中接收。
二、通過依賴注入自動獲取。雖然中間件類的構造函數可以接收注入對象,但是,不推薦在這里接收注入對象,因為這樣會改變注入對象的生命周期。由於中間件類在運行階段只實例化一次,故它的生命周期應與應用程序相等。所以,如果通過構造函數獲取注入的話,由於注入對象長期存在引用,使得服務容器無法釋放它。如果注入對象是用 AddTransient 方法添加到服務集合中,本應該每次使用后釋放,但由於實例被引用,就會導致生命周期變得與應用程序同等,所以,不應該在中間件類的構造函數中來注入,而應該在 Invoke 或 InvokeAsync 方法中進行注入。
來來來,動手,咱們用實例來學習,效果會番十倍的。
先寫一個中間件類。
public class DemoMiddleware { RequestDelegate _next; double mx, my; public DemoMiddleware(RequestDelegate next, double x, double y) { _next = next; mx = x; my = y; } public async Task InvokeAsync(HttpContext context) { double r = mx + my; context.Response.Headers["Compute-Result"] = $"{mx} + {my} = {r}"; await _next(context); } }
注意啊各位,中間件類的構造函數,一般都要一個 RequestDelegate 的參數,干嗎用的呢?就是讓你可以調用下一個中間件,它代表的是鏈條上的下一個中間件。
兩個 double 類型的參數才是我們真正要傳的參數。在 InvokeAsync 方法中,我做了一個簡單處理,把兩個參數的值相加,然后通過 HTTP 頭返回給客戶端。
為了使下一個中間件能被調用,記得在 InvokeAsync 方法的最后調用一下 _next 字段。
現在,回到 Startup 類,找到 Configure 方法,我們使用剛剛定義的中間件類。
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { …… app.UseMiddleware<DemoMiddleware>(15.33d, 8.96d); }
參數就是通過 UseMiddleware 方法來傳遞給中間件類的,有幾個就傳幾個,該什么類型就什么類型,因為參數簽名是 object 類型,有容乃大,它能兼容所有類型。
現在我們來測試一下。運行之后,你發現瀏覽器得到的是 404,對的,前面老周說了,中間件鏈條的最后一個中間件就是 404。我們沒有向客戶端回寫任何內容,所以返回 404 太正常了。但是,代碼其實是成功執行了的。
你可以打開抓包工具(比如 F12 工具里面就有),然后刷新一下,隨后你去看一下返回消息的 Http 頭。
看到了吧,說明中間件是執行了的。
下面,我們用依賴注入,在中間件中獲取參數。
public class Demo2Middleware { RequestDelegate _next; public Demo2Middleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context, IHostingEnvironment env) { context.Response.Headers["app-name"] = env.ApplicationName; context.Response.Headers["env-name"] = env.EnvironmentName; await _next(context); } }
在 Invoke 方法中,第一個參數是 HttpContext,這個東東是必須的,然后后面我們獲取一個 IHostingEnvironment,這個在應用程序初始化時由框架自動添加到服務容器中,它會自動注入到 Invoke 方法中。在上面代碼中,我只是把應用名稱和運行環境名稱添加到 HTTP 頭中。
隨后在 Startup 類的 Configure 方法中,也用上這個中間件。
app.UseMiddleware<Demo2Middleware>();
運行之后,刷新瀏覽器,再抓包,你就看到在 Http Header 集合中多了幾個東東。
爽吧,中間件也順利執行了。
其實,你仔細一看就會發現,這個 Invoke / InvokeAsync 方法的注入方式與 Startup 類的 Configure 方法是一樣的。Configure 方法的第一個參數是必須的,型類為 IApplicationBuilder,后面的參數就是注入的。
好了,向中間件傳參數的兩種方法都介紹完了。
最后,順便說一下,“五一”假期前老周可能會直播一次 ASP.NET Core ,但不保證會開播,如果這幾天沒弄的話,就要等“五一”假期之后再開播。