如何自訂 ASP.NET Core Web API 的錯誤回應訊息

栏目: ASP.NET · 发布时间: 6年前

内容简介:我看過有無數企業在設計 Web API 的時候,會將所有可能的回應訊息,無論成功或失敗,全部一律回應 HTTP 狀態碼在許多企業都會規範錯誤代碼管理,也就是所有 API 的回應訊息通常有一定的格式,而且不同的狀態碼所代表的意義,也會依據不同系統而有所不同,有些系統的狀態碼可能多達數百個到數千個之多。我想大部分的人應該都同意,使用 RESTful 架構開發 API 就應該多利用 HTTP 狀態碼,盡量將錯誤代碼都透過 HTTP 狀態碼來回應。但現實上,幾百個不同的代碼,怎麼可能找的到相對應的 HTTP 狀態

我看過有無數企業在設計 Web API 的時候,會將所有可能的回應訊息,無論成功或失敗,全部一律回應 HTTP 狀態碼 200 (OK)。但這樣的設計完全違反 RESTful 架構精神,我們應該盡可能透過狀態碼表明回應的狀態才對。明明是一份不 OK 的訊息,硬要回應 OK 真的很怪。我就來透過這篇文章,告訴你為什麼大家會這樣設計,以及怎樣設計才正確。

建立一個範例 API 專案

  1. 先用 dotnet --info 確認一下目前使用的 .NET Core 版本資訊。

    我目前使用的版本是:

    • .NET Core SDK: 2.1.401
    • .NET Core Runtimes: 2.1.3
      • Microsoft.NETCore.App
      • Microsoft.AspNetCore.App

    詳細如下:

    .NET Core SDK (reflecting any global.json):
     Version:   2.1.401
     Commit:    91b1c13032
    
    Runtime Environment:
     OS Name:     Windows
     OS Version:  10.0.17134
     OS Platform: Windows
     RID:         win10-x64
     Base Path:   C:\Program Files\dotnet\sdk\2.1.401\
    
    Host (useful for support):
      Version: 2.1.3
      Commit:  124038c13e
  2. 建立並啟動一個全新 API 專案

    建立專案

    dotnet new webapi --name api1
    cd api1

    啟動專案

    • 設定 ASPNETCORE_ENVIRONMENT 環境變數為 Production 可避免顯示預設的開發人員錯誤頁面。
    • 使用 dotnet watch run 執行專案,會自動監視所有程式變更,有變更就會自動重新編譯。
    • 加入 --no-launch-profile 就不會讀取 .vscode/launch.json 的啟動設定。
      • 當透過 Visual Studio Code 開啟專案時,預設會建立這個檔案,而 dotnet rundotnet watch run 預設會讀取這個檔案設定。
    set ASPNETCORE_ENVIRONMENT=Production
    dotnet watch run --no-launch-profile

    連結到以下網址: https://localhost:5001/api/values/1 查看內容。

設定發生例外的條件

  1. 修改 Controllers/ValuesController.cs 檔案

    我們直接修改 public ActionResult<string> Get(int id) 方法,加入會發生例外狀況的程式碼。範例如下:

    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        if (id <= 0)
        {
            throw new ArgumentException("id must larger than zero.");
        }
        return "value";
    }
  2. 測試錯誤畫面

    此時你連到 https://localhost:5001/api/values/0 就會得到一個 HTTP 狀態碼 500 的錯誤訊息,一個回應內容長度為 0 的訊息。

為什麼大家習慣用 HTTP 狀態碼 200 來回應訊息?

在許多企業都會規範錯誤代碼管理,也就是所有 API 的回應訊息通常有一定的格式,而且不同的狀態碼所代表的意義,也會依據不同系統而有所不同,有些系統的狀態碼可能多達數百個到數千個之多。

我想大部分的人應該都同意,使用 RESTful 架構開發 API 就應該多利用 HTTP 狀態碼,盡量將錯誤代碼都透過 HTTP 狀態碼來回應。但現實上,幾百個不同的代碼,怎麼可能找的到相對應的 HTTP 狀態碼可以表示。我只能說 HTTP 狀態碼只是對 HTTP 回應的訊息進行大致分類,詳細的錯誤訊息與代碼,當然是寫在 HTTP 回應內文中。

對一個剛學習 ASP.NET Web API 開發的人來說,可能不太清楚怎樣自訂 HTTP 500 的錯誤回應訊息。所以一般人在專案時程壓力下,索性就直接 try/catch 所有錯誤,全以 HTTP 200 來回應了。這實在不是明智之舉,因為這會造成整體系統的技術債,對長遠的可維護性來說相當不利,程式碼的可讀性也會變差。

所以這篇文章,就來說說如何在 ASP.NET Core Web API 中自訂一份含有完整訊息的 HTTP 500 錯誤回應。

如何自訂 HTTP 500 錯誤訊息內容

在使用 ASP.NET Web API 2 的 例外處理 時,有個 HttpResponseException 類別可用,它可以讓你自訂一個 HTTP 回應的例外,而且可以自訂狀態碼與回應訊息內容。

不過這個類別已經從 ASP.NET Core 完整移除,原因在 Why no HttpResponseException? #4311 討論串中有提到。負責整個 ASP.NET Core 設計架構的 David Fowler 明確提到「 我們不希望人們使用例外來控制程式執行的流程,因為大部分人在商業邏輯中使用例外的方式都錯的離譜。 」。原文如下:

Just the classic, " we don't want people using exceptions for control flow " paradigm. People did things like use it from within their business logic which is all sorts of wrong. I can't speak to the performance issues because I haven't measured but throwing an exception that is meant to be caught to get some data is worse than just returning that result.

所以,要寫出正確的程式碼,要先從正確的觀念著手。

這邊我點出兩種需要例外管理的情境:

  1. 可以在 Controller 中捕捉到的例外。
  2. 無法在 Controller 中捕捉到的例外。

第 1 種情境,其實是相對單純。因為你可以直接從 Controller 內,透過 ActionResult 來回應自定的錯誤訊息:

public ActionResult<string> Get(int id)
{
    if (id <= 0)
    {
        return StatusCode((int)HttpStatusCode.InternalServerError, new {
            errorno = 1,
            message = "id must larger than zero."
        });
    }

    return "value";
}

你也可以透過 ASP.NET Core 內建的例外處理機制 進行捕捉例外,例如透過 Exception Filter 來回應例外的回應訊息。

如果真的不容易捕捉,也可以透過 try/catch 並搭配 IActionResult 來回應含有 HTTP 500 狀態碼的回應訊息。當然,這不是個好做法,建議不要透過 try/catch 擾亂程式流程。如我不用 try/catch 的話,就要考慮第 2 種情境的解法。

第 2 種情境,就如我所說的,當你不想用 try/catch 捕捉例外,那就要從更底層的 Middleware 著手,你可以利用 UseExceptionHandler 去捕捉所有沒被 ASP.NET Core Web API 捕捉到例外狀況。

在預設的情況下,如果 Kestrel 捕捉到應用程式未處理的例外狀況,會直接回應 HTTP 500 狀態碼,而且沒有任何內容。如果要回應一些具體的訊息,你可以參考 處理 ASP.NET Core 中的錯誤 文件,裡面有提到許多處理錯誤的方法,也提到了許多處理錯誤的觀念,建議大家可以認真看過一遍。

這份文件有提到 app.UseExceptionHandler("/error"); 方法,可以讓你設定自訂的例外狀況處理頁面。不過,這裡的範例是以 Razor Pages 與 MVC 錯誤頁面回應。我這邊特別寫一組 Web API 的例子,如下範例:

  1. 加一條 app.UseExceptionHandler("/api/error");Startup.csConfigure() 方法中。

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/api/error");
        app.UseHsts();
    }
  2. 然後新增一個 ErrorController 處理所有例外錯誤

    這邊我先自訂一個 ErrorResponseVM 模型類別,專門用來回應制式的例外錯誤訊息。

    public class ErrorResponseVM
    {
        public int errorno { get; set; }
        public string message { get; set; }
    }

    完整的 ErrorController 範例程式如下:

    [Route("api/error")]
    public class ErrorController : Controller
    {
        [AllowAnonymous]
        public ActionResult<ErrorResponseVM> Error()
        {
            return new ErrorResponseVM()
            {
                errorno = 1,
                message = "Normal Error"
            };
        }
    }

    ※ 請記得引用 using Microsoft.AspNetCore.Authorization; 命名空間。

如此一來,就可以自動捕捉所有例外狀況了,包含不是 ASP.NET Core 產生的例外,也都可以透過 /api/error 來回應訊息。

如何取得真正的 Exception 例外物件

感謝 .NET Core 內建的 DI (相依性注入) 機制,讓這一切變得相當簡單。想取得例外,只要一行程式碼,就可以取得例外處理的特性物件:

var ex = HttpContext.Features.Get<IExceptionHandlerFeature>();

接著只要透過 ex.Error 就可以取得完整的例外物件。

完整範例如下:

[Route("api/error")]
public class ErrorController : Controller
{
    [AllowAnonymous]
    public ActionResult<ErrorResponseVM> Error()
    {
        var ex = HttpContext.Features.Get<IExceptionHandlerFeature>();
        if (ex != null)
        {
            return StatusCode(
                (int) HttpStatusCode.InternalServerError,
                new ErrorResponseVM()
                {
                    errorno = 999,
                    message = ex.Error.Message
                });
        }
        else
        {
            return StatusCode(
                (int) HttpStatusCode.InternalServerError,
                new ErrorResponseVM()
                {
                    errorno = 999,
                    message = "ERROR OCCURRED!"
                });
        }

    }
}

結語

本篇文章,詳細說明在 ASP.NET Core Web API 中自訂錯誤訊息的方法,幫助大家更容易寫出 RESTful 的 Web API 服務。用正確的觀念寫 Code,一直是我特別強調的開發方式。

雖然有時候專案在趕,真的沒時間、沒預算去改。但有空的時候,身為一個開發人員,最好還是要花點時間 Review 曾經寫過的程式碼 (即便 Code 可能不是你寫的),透過重構不斷改善程式碼品質,一來可以改善程式碼品質,二來也可以訓練自己對程式碼的好壞(Code Smell)更加敏感。

相關連結


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Non-Obvious

Non-Obvious

Rohit Bhargava / Ideapress Publishing / 2015-3-29 / USD 24.95

What do Disney, Bollywood, and The Batkid teach us about how to create celebrity experiences for our audiences? How can a vending-machine inspire world peace? Can being imperfect make your business mo......一起来看看 《Non-Obvious》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具