最近博客更新頻率慢了些,緣由有三:數組
其一,最近老周每星期六都錄 ASP.NET Core 的直播,有些內容在視頻裏講過,就不太想在博客裏面重復。有興趣的話能夠去老周的微博看,或者去一直播,直播賬號與微博賬號是綁定的;瀏覽器
其二,最近老周是有些忙,但不是忙寫代碼的事情。而是忙着「尋寶藏」。服務器
其三,每一個星期至少也要抽出一天的時間,跟妹子出去浪。準確地說,應該叫對象(Object),是經過構造函數認識的,已經順利運行有五個月了。目前狀態良好,內存佔用小,不燒 CPU。性能好,不吃硬件。app
好了,屁話很少說了。今天說說如何向中間件傳參數的事。框架
首先,用宇宙中最通俗的語言介紹一下啥是中間件。瀏覽器或客戶端向服務器發出請求後,HTTP 請求會進入通訊管道,而後由一個個中間件來進行處理,A 處理完了,交給 B;B 處理完了再交給 C……全部中間件就串成一條鏈,直到最後一箇中間件(HTTP 404)。處理完後就把結果返回給調用者。異步
因此說,中間件就是一個處理 HTTP 請求的「零件」,好比,你能夠定義一箇中間件,對請求中的某些數據進行解密或者驗證。或者,你能夠定義一箇中間件來添加本身定義的 HTTP 頭。async
中間件在代碼中由一個 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 的方法。因此,要想自定義的中間件類起做用,你應該遵照這個約定。
咱們今天討論的重點是向中間件傳參數,要能傳參的話,就必須寫一箇中間件類。先說說,怎麼傳參。方法有二。
1、IApplicationBuilder 的 UseMiddleware 擴展方法,此方法的參數列表中,最後一個是加了 params 關鍵字的 object 數組。這個就是用來傳參數的,並且參數的個數是不肯定的。傳入的參數從中間件類的構造函數中接收。
2、經過依賴注入自動獲取。雖然中間件類的構造函數能夠接收注入對象,可是,不推薦在這裏接收注入對象,由於這樣會改變注入對象的生命週期。因爲中間件類在運行階段只實例化一次,故它的生命週期應與應用程序相等。因此,若是經過構造函數獲取注入的話,因爲注入對象長期存在引用,使得服務容器沒法釋放它。若是注入對象是用 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 ,但不保證會開播,若是這幾天沒弄的話,就要等「五一」假期以後再開播。