上一篇中,老周給大伙伴們扯了有關 ASP.NET Core 中異常處理的簡單方法。按照老周的優良作風,我們應該順著這個思路繼續挖掘。 本文老周就不自量力地介紹一下如何使用 MVC Filter 來處理異常。MVC 模型(當然適用於 Razor Page 、Web API 模型)可以用一系列的 Fil ...
上一篇中,老周給大伙伴們扯了有關 ASP.NET Core 中異常處理的簡單方法。按照老周的優良作風,我們應該順著這個思路繼續挖掘。
本文老周就不自量力地介紹一下如何使用 MVC Filter 來處理異常。MVC 模型(當然適用於 Razor Page 、Web API 模型)可以用一系列的 Filter 來對請求與回應消息進行過濾處理。其中,在 Microsoft.AspNetCore.Mvc.Filters 命名空間下,你會發現有兩個介面,它們跟異常處理有關:
IExceptionFilter:實現 OnException 方法,可以自定義回傳給客戶端的異常信息。
IAsyncExceptionFilter:跟上面的一樣的,只不過這廝支持非同步等待而已。
在實現處理異常的 Filter 時,傳給 OnException / OnExceptionAsync 方法的有一個 ExceptionContext 類型參數,我們可以通過它來設置自定義的返回結果。
訪問 Exception 屬性,你可以得到相關的異常實例,當然這個屬性是可寫的,所以你可以獲取異常實例後,將它改為其他異常實例,再重新賦給這個屬性,比如,你用你自己編寫的異常類來重新封裝。通過 Result 屬性設置返回結果,這個與 MVC Action 方法的返回方法一樣,不同的是,在 Action 方法中,你可以調用 Controller 基類的方法來返回對應的 Result ,而對於 Result 屬性,你必須顯式地去創建實現了 IActionResult 介面的類型實例。
另外,值得註意的是,ExceptionContext 類還有一個 ExceptionHandled 屬性,該屬性值可讀可寫,主要是用於標識當前發生的異常是否已經過處理。這主要是應對 Filter 的執行順序的,一種情況是你可能使用了多個 Filter 來處理異常,在處理過程中你就可以將這個屬性值設為 true 以表示這個錯誤已處理過了,後面的就不必處理了;另一種情況是,以 Attribute 方式使用的 Filter 的優先順序會比全局使用的 Filter 高,也許在 Attribute 上我沒有對異常進行處理,那麼到了全局 Filter 執行的時候,我就可以檢查一下這個屬性,如果沒有處理就進行一下處理。關於 Attribute 方式使用 Filter 老周隨後會說的,這裡先提一下。
好了,咱們先說說如何實現自己的異常處理 Filter,其實很簡單,看下麵代碼。
public class MyExceptionFilter : IExceptionFilter, IFilterMetadata { public void OnException(ExceptionContext context) { if(context.ExceptionHandled == false) { string msg = context.Exception.Message; context.Result = new ContentResult { Content = msg, StatusCode = StatusCodes.Status200OK, ContentType = "text/html;charset=utf-8" }; } context.ExceptionHandled = true; //異常已處理了 }
在 OnException 方法中,我直接獲取異常信息,然後用一個 ContentResult 對象來返回,這個是類似於 MVC 中 Controller . Action 方法返回結果,我這裡簡單地以 HTML 文本形式返回,一旦處理到異常,應用程式會自動把這個 Result 返回給客戶端。
你可能發現了,我除了實現 IExceptionFilter 介面外,還實現了一個 IFilterMetadata 介面,這個介面是必須的,不然待會兒我們無法應用這個 Filter 了,為什麼呢,等一下你就會明白了。
這裡實現的這個是同步調用的,如果你希望有一個可非同步等待的版本,那麼,你就順便實現一下 IAsyncExceptionFilter 介面。把上面的代碼改為:
public class MyExceptionFilter : IExceptionFilter, IAsyncExceptionFilter, IFilterMetadata { public void OnException(ExceptionContext context) { if(context.ExceptionHandled == false) { string msg = context.Exception.Message; context.Result = new ContentResult { Content = msg, StatusCode = StatusCodes.Status200OK, ContentType = "text/html;charset=utf-8" }; } context.ExceptionHandled = true; //異常已處理了 } public Task OnExceptionAsync(ExceptionContext context) { OnException(context); return Task.CompletedTask; } }
好了,接下來咱們得考慮怎麼用它了。在 Startup.ConfigureServices 方法中,添加 MVC 功能後可以把咱們自己寫的 Filter 添加進去。
public void ConfigureServices(IServiceCollection services) { services.AddMvc(opt => { opt.Filters.Add<MyExceptionFilter>(); }); }
上面代碼添加 Filter 後,是用於全局的,說白了,當應用程式內不管哪個 Controller 裡面發生的異常,都會經過咱們添加的 Filter 處理。
現在我們測試一下這個異常處理的 Filter 起到什麼作用。為了不影響測試,請把 Configure 方法中這段代碼刪除。
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
變成這樣
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseMvc(); }
然後,隨便弄段代碼來測試。
[HttpPost("/code")] public IActionResult SubmitSome(int val) { if(val <= 0) { throw new ArgumentException("號碼不能小於或等於 0。"); } return Content($"恭喜你,中獎了。\n中獎號碼為:{val}", "text/html;charset=utf-8"); }
這個邏輯很簡單,就是在前臺頁面輸入一個數值,然後 POST 上來,如果數值不是大於 0 的值就拋異常。
然後我故意輸入一個 -10。
POST 後在伺服器上引發異常。
繼續執行,讓 Filter 對異常進行處理。
最後,異常信息就返回給瀏覽器了。
這樣說明咱們寫的 Filter 起作用了。
剛剛說過,在 ConfigureServices 方法中添加的 Filter 是用於全局的,如果我們的項目中有個別的 Controller 或者 Controller 中的個別方法,希望使用專門的 Filter 去處理異常,這時候就可以考慮以 Attribute 的方式去處理。
要用 Attribute 方式處理異常,需要實現 ExceptionFilterAttribute 抽象類。該抽象類已實現了咱們上面提到過的幾個介面。
這個類還實現了 IOrderedFilter 介面,可以用來安排多個 Attribute 實例在處理異常上的順序(假設你用了多個實例來處理)。
下麵咱們自己實現一個 Attribute ,用來處理異常。
public class MyExceptionFilterAttribute : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { var ex = context.Exception; // 構建錯誤信息對象 var dic = new Dictionary<string, object> { ["err_code"] = 80250, ["err_msg"] = ex.Message, ["err_sol"] = "建議攜帶你的數據到醫院做檢查。" }; // 設置結果 context.Result = new JsonResult(dic); context.ExceptionHandled = true; } public override Task OnExceptionAsync(ExceptionContext context) { OnException(context); return Task.CompletedTask; } }
上面代碼中,我以 JSON 格式返回錯誤數據。
這個 Attribute 可以用於類與方法,然後咱們用 Web API 來測試。
[Route("api/[controller]")] public class DemoController : Controller { [HttpGet] [MyExceptionFilter] public IActionResult Compute(int m, int n) { if (m < 0 || n < 0) { throw new Exception("數值不能小於 0。"); } return Json(new { num1 = m, num2 = n, result = m + n }); } }
此處把 attrbute 用到方法上。
運行應用程式,然後請出 Postman 大叔來幫我們測試 Web API。為參數 m 和 n 賦值,然後以 GET 方式發送請求。
獲得正確的結果,現在咱們提交小於 0 的參數。就會返回剛剛自定義的錯誤。
好了,今天的內容就說到這裡,下次有空繼續扯。