異常處理在程式設計中非常重要,一是為了給用戶友善提示,二是為了程式安全。在 ASP.NET Core 中,預設已經為我們提供了許多解決方案,以下就來總結一下。
1. 繼承 Controller,覆寫 OnActionExecuted
使用 VS 新增 Controller 時,預設都會繼承一個 Controller 類別,覆寫 OnActionExecuted,加上異常處理即可。一般情況下我們會新增一個 BaseController,讓所有 Controller 繼承 BaseController。程式碼如下
public class BaseController : Controller
{
public override void OnActionExecuted(ActionExecutedContext context)
{
var exception = context.Exception;
if (exception != null)
{
context.ExceptionHandled = true;
context.Result = new ContentResult
{
Content = $"BaseController錯誤 : { exception.Message }"
};
}
base.OnActionExecuted(context);
}
}
這種處理方式的優點當然是簡單,缺點也很明顯,如果 cshtml 頁面拋錯,就完全捕捉不了。當然如果專案本身就是一個 Web API 專案,沒有 View,這還是可以的。
2. 使用 ActionFilterAttribute
ActionFilterAttribute 是一個屬性,本身實作了 IActionFilter 及 IResultFilter,所以不管是 Action 裡拋錯,還是 View 裡拋錯,理論上都可以捕捉。我們新增一個 ExceptionActionFilterAttribute,覆寫 OnActionExecuted 及 OnResultExecuted,加上異常處理,完整程式碼如下。
public class ExceptionActionFilterAttribute:ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
var exception = context.Exception;
if (exception != null)
{
context.ExceptionHandled = true;
context.Result = new ContentResult
{
Content = $"錯誤 : { exception.Message }"
};
}
base.OnActionExecuted(context);
}
public override void OnResultExecuted(ResultExecutedContext context)
{
var exception = context.Exception;
if (exception != null)
{
context.ExceptionHandled = true;
context.HttpContext.Response.WriteAsync($"錯誤 : {exception.Message}");
}
base.OnResultExecuted(context);
}
}
使用方式有兩種:
- 在 Controller 裡加上
[TypeFilter(typeof(ExceptionActionFilter))]標籤。 - 在 Startup 裡以 Filter 方式全域注入。
services.AddControllersWithViews(options =>
{
options.Filters.Add<ExceptionActionFilterAttribute>();
})
3. 使用 IExceptionFilter
我們知道,ASP.NET Core 提供了 5 類 Filter,IExceptionFilter 是其中之一,顧名思義,這就是用來處理異常的。ASP.NET Core 中 ExceptionFilterAttribute 已經實作了 IExceptionFilter,所以我們只需繼承 ExceptionFilterAttribute,覆寫其中的方法即可。同樣新增 CustomExceptionFilterAttribute 繼承 ExceptionFilterAttribute,覆寫 OnException,加入異常處理,完整程式碼如下
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
context.ExceptionHandled = true;
context.HttpContext.Response.WriteAsync($"CustomExceptionFilterAttribute錯誤:{context.Exception.Message}");
base.OnException(context);
}
}
注意,ExceptionFilterAttribute 還提供了非同步方法 OnExceptionAsync,這兩個處理方法只需覆寫一個即可,如果兩個都覆寫,兩個方法都會執行一次。
使用方式有兩種:
- 在 Controller 裡加上
[CustomExceptionFilter]標籤。 - 在 Startup 裡以 Filter 方式全域注入。
services.AddControllersWithViews(options =>
{
options.Filters.Add<CustomExceptionFilterAttribute>();
})
4. 使用 ExceptionHandler
在 Startup 裡,VS 新增的專案會預設加上
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
上面這段程式碼,大意為:開發環境使用 app.UseDeveloperExceptionPage()。生產環境會跳轉至 /Home/Error 頁面。
UseDeveloperExceptionPage 會給出具體錯誤資訊,具體包括 Stack trace、Query、Cookies、Headers 四部分。.NET Core 3.1 後有些改動,如果請求頭加上 accept:text/html,就回傳 HTML 頁面,不加的話,只會回傳錯誤。
注意:app.UseDeveloperExceptionPage() 應該放在最前面。
if (!env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
await context.Response.WriteAsync($"error:{exceptionHandlerPathFeature.Error.Message}");
});
});
}
5. 自訂 Middleware 處理
透過 Middleware 全域處理。
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (System.Exception ex)
{
//處理異常
}
}
}