例外處理(Exception Handler)算是程式開發蠻重要的一件事,尤其程式暴露在外,要是不小心顯示了什麼不該讓使用者看到的東西就糟糕了。
要在 ASP.NET Core 做一個通用的 Exception Handler 可以透過 Middleware 或 Filter,但兩者之間的執行週期確大不相同。
本篇將介紹 ASP.NET Core 透過 Middleware 及 Filter 異常處理的差異。
iT 邦幫忙 2018 鐵人賽 - Modern Web 組參賽文章:
[Day17] ASP.NET Core 2 系列 - 例外處理 (Exception Handler)
實做 Exception Handler 前,需要先了解 Middleware 及 Filter 的特性。
可以參考這兩篇:
Exception Filter
Exception Filter 僅能補捉到 Action 及 Action Filter 所發出的 Exception。
其它的類型的 Filter 或 Middleware 產生的 Exception,並沒有辦法透過 Exception Filter 攔截。
如果要做全站的通用的 Exception Handler,可能就沒有這麼合適。
Exception Filter 範例:
ExceptionFilter.cs
1 2 3 4 5 6 7 8 9 10
| public class ExceptionFilter : IAsyncExceptionFilter { public Task OnExceptionAsync(ExceptionContext context) { context.HttpContext.Response .WriteAsync($"{GetType().Name} catch exception. Message: {context.Exception.Message}"); return Task.CompletedTask; } }
|
Exception Filter 全域註冊:
Startup.cs
1 2 3 4 5 6 7 8 9 10 11
| public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(config => { config.Filters.Add(new ExceptionFilter()); }); } }
|
除非註冊了兩個以上的 Exception Filter,不然 Filter 註冊的先後順序並不重要,執行順序是依照 Filter 的類型,同類型的 Filter 才會關係到註冊的先後順序。
Exception Middleware
Middleware 註冊的層級可以在 Filters 的外層,也就是說所有的 Filter 都會經過 Middleware。
如果再把 Exception Middleware 註冊在所有 Middleware 的最外層,就可以變成全站的 Exception Handler。
Exception Handler 層級示意圖如下:
Exception Middleware 範例:
ExceptionMiddleware.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class ExceptionMiddleware { private readonly RequestDelegate _next;
public ExceptionMiddleware(RequestDelegate next) { _next = next; }
public async Task Invoke(HttpContext context) { try { await _next(context); } catch (Exception ex) { await context.Response .WriteAsync($"{GetType().Name} catch exception. Message: {ex.Message}"); } } }
|
Exception Middleware 全域註冊:
Startup.cs
1 2 3 4 5 6 7 8 9
| public class Startup { public void Configure(IApplicationBuilder app) { app.UseMiddleware<ExceptionMiddleware>(); } }
|
Middleware 的註冊順序很重要,越先註冊的會包在越外層。
把 ExceptionMiddleware 註冊在越外層,能涵蓋的範圍就越多。
Exception Handler
ASP.NET Core 有提供 Exception Handler 的 Pipeline,底層就是用上述 Exception Middleware 的做法,在 Application Builder 使用 UseExceptionHandler
指定錯誤頁面。
Startup.cs
1 2 3 4 5 6 7 8 9
| public class Startup { public void Configure(IApplicationBuilder app) { app.UseExceptionHandler("/error"); } }
|
用以下範例模擬錯誤發生:
Controllers\HomeController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| using Microsoft.AspNetCore.Mvc;
namespace MyWebsite.Controllers { public class HomeController : Controller { public void Index() { throw new System.Exception("This is exception sample from Index()."); }
[Route("/api/test")] public string Test() { throw new System.Exception("This is exception sample from Test()."); }
[Route("/error")] public IActionResult Error() { return View(); } } }
|
Views\Shared\Error.cshtml
1 2 3 4 5 6 7 8 9
| <!DOCTYPE html> <html> <head> <title>Error</title> </head> <body> <p>This is error page.</p> </body> </html>
|
當連入 http://localhost:5000/
發生錯誤後,就會回傳顯示 This is error page. 的頁面。
注意!不會轉址到 http://localhost:5000/error
,而是直接回傳 HomeController.Error()
的內容。
ExceptionHandlerOptions
如果網站中混用 Web API,當 API 發生錯誤時,依然回傳 HomeController.Error()
的內容,就會顯得很奇怪。UseExceptionHandler
除了可以指派錯誤頁面外,也可以自己實作錯誤發生的事件。
Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Startup { public void Configure(IApplicationBuilder app) { app.UseExceptionHandler(new ExceptionHandlerOptions() { ExceptionHandler = async context => { bool isApi = Regex.IsMatch(context.Request.Path.Value, "^/api/", RegexOptions.IgnoreCase); if (isApi) { context.Response.ContentType = "application/json"; var json = @"{ ""Message"": ""Internal Server Error"" }"; await context.Response.WriteAsync(json); return; } context.Response.Redirect("/error"); } }); } }
|
這次特別處理了 API 的錯誤,當連入 http://localhost:5000/api/*
發生錯誤時,就會回傳 JSON 格式的錯誤。
1 2 3
| { "Message": "Internal Server Error" }
|
同時把 MVC 發生錯誤的行為,改用轉址的方式轉到 http://localhost:5000/error
。
UseDeveloperExceptionPage
通常在開發期間,還是希望能直接看到錯誤資訊,會比較方便除錯。UseDeveloperExceptionPage
是 ASP.NET Core 提供的錯誤資訊頁面服務,可以在 Application Builder 注入。
在 Startup.Configure
注入 IHostingEnvironment
取得環境變數,判斷在開發階段才套用,反之則用 Exception Handler。
Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/error"); } } }
|
env.IsDevelopment()
是從 ASPNETCORE_ENVIRONMENT
而來。
詳細情參考這篇:[鐵人賽 Day16] ASP.NET Core 2 系列 - 多重環境組態管理 (Multiple Environments)
開發環境的錯誤資訊頁面如下:
參考
Introduction to Error Handling in ASP.NET Core