ASP.NET Core 5種例外處理方案

ASP.NET Core 5種例外處理方案

例外處理在程式設計中非常重要,一來可以給使用者友好提示,二來也是為了程式安全。

最後更新 2021/4/29 上午9:27
.NET技术栈
預計閱讀 4 分鐘
分類
ASP.NET Core
標籤
.NET C# ASP.NET Core 安全 例外

異常處理在程式設計中非常重要,一是為了給用戶友善提示,二是為了程式安全。在 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);
    }
}

使用方式有兩種:

  1. 在 Controller 裡加上 [TypeFilter(typeof(ExceptionActionFilter))] 標籤。
  2. 在 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,這兩個處理方法只需覆寫一個即可,如果兩個都覆寫,兩個方法都會執行一次。

使用方式有兩種:

  1. 在 Controller 裡加上 [CustomExceptionFilter] 標籤。
  2. 在 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)
        {
           //處理異常
        }
   }
}
繼續探索

延伸閱讀

更多文章