前言
Middleware 在 ASP.NET Core 開發時是個很常見的功能,概念很像 ASP.NET Application Life cycle 管線的 Handler 機制 (若對於 Life Cycle 想了解更多可以看之前寫的文章 Application Life Cycle),提供開發者可以在 Request 進入到 Application 前加上客製化的邏輯,實務上用起來挺方便的也蠻好用的,在加上 middleware 相關邏輯後也會加上單元測試,確保所撰寫的邏輯有驗證過且是沒有問題,也避免在 middleware 異常時 Devops 同仁半夜叫你起床處理(萬萬不可阿,這篇文章要說明的是如何在 middleware 加上 unittest,對有經驗的開發者相信是一塊小蛋糕,內容若有問題歡迎提出來一起討論。
建立檢查 Token Middleware
舉一個常用的例子,實務上在與第三方在做串接時需要 token 作為對方請求的驗證,作為過濾不必要的請求以節省伺服器的資源。全部 API 接口都需要做 token 檢查因此將此邏輯檢查可以放在 Middleware,首先建立一個新的 .NET Core 3.1 WebAPI 專案,接著建立 TokenVerificationMiddleware.cs 類別,在此類別中加入下列代碼
public class TokenVerificationMiddleware { private readonly RequestDelegate _next; public TokenVerificationMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (context.Request.Headers.ContainsKey("token") && context.Request.Headers["token"] == "marcusblog") { await _next.Invoke(context); } else { context.Response.StatusCode = 403; // UnAuthorized await context.Response.WriteAsync("Invalid request token"); return; } } }在此類別的代碼中定義了當請求來時檢查 Request 的 Header 資訊是否有包含 token 關鍵字,token 有的話是否等於 marcusblog,如果都符合就讓此請求往下一步進行進入到下一個流程像是 Action 邏輯;相反的如果不符合 token 檢查邏輯,就會回傳 403 等 Status Code。
當建立好檢查 Token 的 Middleware 之後,接著開啟專案中的 StartUp.cs 類別在 Configure 方法中加上以下代碼
app.UseMiddleware上述是使用內建的 UseMiddleware 方法並定義指定的 Middleware 類別,另外還可以使用擴充方法(Extensions Method)達到使用 Middleware,也是個人比較推薦的方式,將要使用的 Middleware 統一集中在特定的class中代碼在閱讀上較為容易理解,使用方式是新增 ApplicationBuilderExtension 類別,裡面新增 UseTokenVerificationMiddleware 方法並定義回傳 IApplicationBuilder 型別,內容與在 startup.cs 的一樣();
public static class ApplicationBuilderExtension { public static IApplicationBuilder UseTokenVerificationMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware接著再回到 Configure 方法將 UseTokenVerificationMiddleware 取代原本的方法(); } }
//app.UseMiddleware開啟偵錯進行測試,可以發現網頁打開會因為 Request 中沒有在 Header 中帶 token 參數出現 Invalid request token,畫面如下(); app.UseTokenVerificationMiddleware();
加上單元測試
一般來說請求進到 Application 的 Request 請求的內容會存在於 HttpContext 中,接著再把 HttpContext 的請求傳遞到所指定的 Middleware 邏輯中,也就是在建構子中的參數 HttpContext,那麼要進行單元測試的話第一步是要先模擬請求的 HttpContext 物件,這邊可以使用 DefaultHttpContext 達到這目的,DefaultHttpContext 繼承了 HttpContext 抽象類別,讓我們在單元測試中更為方便,舉例來說如果要模擬 hpptContext 物件並在 Header 加上 token 內容用下列方式就可以達到
var context = new DefaultHttpContext(); context.Request.Headers.Add("token", "this is a book");DefaultHttpContext 還可以指定測試的路徑(Path)、內容(Body)、ContentType 或是可以指定 Cookie Form 及 QueryString 等常見的設定值,使用上相當的簡易與方便,放在測試案例中代碼如下
public class TokenVerificationMiddlewareTest { private TokenVerificationMiddleware _target; [SetUp] public void Initial() { _target = new TokenVerificationMiddleware(null); } [Test] public void Correct_Header_Should_Return_Success() { var context = new DefaultHttpContext(); context.Request.Headers.Add("token", "marcusblog"); var result = _target.Invoke(context); context.Response.StatusCode.Should().Be(200); } [Test] public void Without_Header_Should_Return_UnAuthorized() { var context = new DefaultHttpContext(); var result = _target.Invoke(context); context.Response.StatusCode.Should().Be(403); } [Test] public void Empty_Header_Should_Return_UnAuthorized() { var context = new DefaultHttpContext(); var result = _target.Invoke(context); context.Response.StatusCode.Should().Be(403); } [Test] public void Wrong_Header_Should_Return_UnAuthorized() { var context = new DefaultHttpContext(); context.Request.Headers.Add("token", "wrongtoken"); var result = _target.Invoke(context); context.Response.StatusCode.Should().Be(403); } }以上是使用 nUnit 搭配 FluentAssertions 套件,測試幾種常見的情境像是 token 為空、header 為空、帶錯誤的 token及帶正確的 token 值 測試後的結果 Pass 成功 ! 大功告成,打完收工
感想
在 ASP.NET Core 有了 httpDefaultContext 之後,在針對 Middleware 撰寫單元測試變得方便許多,對有經驗的開發者來說也是小蛋糕一塊,如果有不清楚的地方歡迎一起討論,hope it helps !
參考
為 .NET Core Middleware 加上 Unit Test
DefaultHttpContext Class
ASP.NET Core 執行原理解剖[4]:進入HttpContext的世界
ASP.NET Core 中介軟體
ASP.NET Core 2.1 middlewares part 2: Unit test a custom middleware