内容简介:我看過有無數企業在設計 Web API 的時候,會將所有可能的回應訊息,無論成功或失敗,全部一律回應 HTTP 狀態碼在許多企業都會規範錯誤代碼管理,也就是所有 API 的回應訊息通常有一定的格式,而且不同的狀態碼所代表的意義,也會依據不同系統而有所不同,有些系統的狀態碼可能多達數百個到數千個之多。我想大部分的人應該都同意,使用 RESTful 架構開發 API 就應該多利用 HTTP 狀態碼,盡量將錯誤代碼都透過 HTTP 狀態碼來回應。但現實上,幾百個不同的代碼,怎麼可能找的到相對應的 HTTP 狀態
我看過有無數企業在設計 Web API 的時候,會將所有可能的回應訊息,無論成功或失敗,全部一律回應 HTTP 狀態碼 200
(OK)。但這樣的設計完全違反 RESTful 架構精神,我們應該盡可能透過狀態碼表明回應的狀態才對。明明是一份不 OK 的訊息,硬要回應 OK 真的很怪。我就來透過這篇文章,告訴你為什麼大家會這樣設計,以及怎樣設計才正確。
建立一個範例 API 專案
-
先用
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
-
.NET Core SDK:
-
建立並啟動一個全新 API 專案
建立專案
dotnet new webapi --name api1 cd api1
啟動專案
-
設定
ASPNETCORE_ENVIRONMENT
環境變數為Production
可避免顯示預設的開發人員錯誤頁面。 -
使用
dotnet watch run
執行專案,會自動監視所有程式變更,有變更就會自動重新編譯。 -
加入
--no-launch-profile
就不會讀取.vscode/launch.json
的啟動設定。-
當透過 Visual Studio Code 開啟專案時,預設會建立這個檔案,而
dotnet run
或dotnet watch run
預設會讀取這個檔案設定。
-
當透過 Visual Studio Code 開啟專案時,預設會建立這個檔案,而
set ASPNETCORE_ENVIRONMENT=Production dotnet watch run --no-launch-profile
連結到以下網址:
https://localhost:5001/api/values/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"; }
-
測試錯誤畫面
此時你連到
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.
所以,要寫出正確的程式碼,要先從正確的觀念著手。
這邊我點出兩種需要例外管理的情境:
- 可以在 Controller 中捕捉到的例外。
- 無法在 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 的例子,如下範例:
-
加一條
app.UseExceptionHandler("/api/error");
到Startup.cs
的Configure()
方法中。if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/api/error"); app.UseHsts(); }
-
然後新增一個 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
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》 这本书的介绍吧!