ASP.NET Core 7 自訂 Controller 例外處理

註: 本文中的例外處理指的是處理 controller 中拋出的例外,並將例外轉換為特定的回應,用以簡化每個 Controller 在執行期間要處理的回應。而非使用中介軟體(Middleware) 捕捉全域例外。

假設我們將處理的邏輯放在 Service 層,在處理資料的時候往往就會面對到處理錯誤資料很麻煩的問題。例如在 Controller 接收到 id ,要取得使用者資料,但是將 id 傳入 Service 層後才發現該筆 id 是錯誤的,無法取回資料。就只能回傳 null ,回到外面的 Controller 再確認是否為空,若空就回傳 NotFound(404)。
假設有 100 個 API ,這樣麻煩的事情就要做非常多次,且如果是一次查詢多筆 id ,Service 層要告訴 Controller 是哪個 id 不存在也非常麻煩,可能還要使用 out 多個 failIds 之類的 array 才有辦法判斷。

自訂例外

雖然有許多既有的例外類別可以使用,例如 KeyNotFoundException, ArgumentException ,不過不容易傳遞我們想要的錯誤訊息。

先建立一個傳輸錯誤訊息的物件:
    
public class ErrorMessageDto
{
    public string Field { get; set; } = string.Empty;
    public string Message { get; set; } = string.Empty;
}
    

建立一個可以存放 ErrorMessageDto 的 Exception ,假設這個 Exception 專門用來表示 key 沒有找到的例外和儲存 ErrorMessageDto :
    
/// <summary>
/// 主鍵無效例外
/// </summary>
public class ApiKeyNotFoundException : Exception
{
    public ErrorMessageDto ErrorMessage { get; set; }

    public ApiKeyNotFoundException(ErrorMessageDto errorMessage)
    {
        ErrorMessage = errorMessage;
        Console.WriteLine("ApiKeyNotFoundException: " + errorMessage);
    }
}
    

建立自訂例外篩選器

    
public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter
{
    public int Order => 1000;

    public void OnActionExecuting(ActionExecutingContext context)
    {
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        switch (context.Exception)
        {
            case ApiKeyNotFoundException apiKeyNotFoundException:
                context.Result = new NotFoundObjectResult(apiKeyNotFoundException.ErrorMessage);
                context.ExceptionHandled = true; // 標記此異常已被處理
                break;
        }
    }
}
    

實現 IOrderedFilter 介面必須要有 Order 屬性,此屬性代表篩選器執行的順序,越小越優先。例如 AuthorizationFilter 的 Order 為 -200 ,若要比 AuthorizationFilter 還要優先,則將 Order 指定為 -201 即可。

上面使用 switch 是因為我們可能會再建立多個自訂例外,故直接使用 switch。

將篩選器加入至控制器

在 Program.cs 中的 var app = builder.Build(); 前加入以下程式碼:
    
builder.Services.AddControllers(options =>
{
    options.Filters.Add<HttpResponseExceptionFilter>();
});
    

建立會拋出自訂例外的 Acrion

在自動產生的 HomeController 中增加以下必定會拋出例外的測試用 Action:
    
[HttpGet("/user/{id:int}")]
public IActionResult User(int id)
{
    throw new ApiKeyNotFoundException(new ErrorMessageDto
    {
        Field = nameof(id),
        Message = $"The user id {id} is not found."
    });
}
    

此時只要開啟下面的網頁即可顯示藉由篩選器回應的 404 訊息(記得替換 port)
    
https://localhost:7110/user/5
    



參考資料:
Microsoft.learn - Handle errors in ASP.NET Core web APIs

留言