同事的專案遇到以下需求:依規格實作 WebAPI (考量開發彈性,使用 ASP.NET MVC Controller,未走 ApiController ),規格定義遇到某些狀況需抛回 HTTP 400 Bad Rquest 並以 JSON 格式回傳錯誤訊息。
一開始的寫法如下:
public ActionResult BadRequestFail()
{
Response.SetStatus(HttpStatusCode.BadRequest);
return Content(
"{ \"error\": \"朕不給的,你不能拿!\" }", "application/json");
}
實測不成功。Response.SetStatus(HttpStatusCode.BadRequest) 雖然有傳回 HTTP 400,但 Body 無內容,return 的 Content() 消失無蹤。
經爬文與實驗後,獲得以下心得:
- 使用 Response.SetStatus(HttpStatusCode.BadRequest) 會中止 Reponse,導致 return Content() 被無視。由 System.Web.WebPages.ResponseExtensions 原始碼可證實此點:
publicstaticvoid SetStatus(this HttpResponseBase response, int httpStatusCode)
{
response.StatusCode = httpStatusCode;
response.End();
}
- Response.StatusCode 屬性支援寫入,不用 SetStatus() 改成直接指定 StatusCode = 400 可避免 Response.End()。
- IIS 遇到 StatusCode 400 時預設是顯示自訂錯誤頁面,也會忽略 return Content() 內容。如要強制回傳結果,需加上 Response.TrySkipIisCustomErrors = true。
綜合上述結論,修改程式如下:
public ActionResult BadRequest()
{
//用SetStatus()會有副作用,阻止傳回Content
Response.StatusCode = 400;
//設定TrySkipIisCustomErrors,停用IIS自訂錯誤頁面
Response.TrySkipIisCustomErrors = true;
return Content(
"{ \"error\": \"朕不給的,你不能拿!\" }", "application/json");
}
HTTP 400 Bad Request 並傳回 error 訊息,測試成功!
另外,systen.webServer 有個 <httpErrors existingResponse="PassThrough" /> 設定也會影響上述行為。預設值為 Auto,由 Response.TrySkipIisCustomErrors 屬性決定是否使用 IIS 自訂錯誤頁面;若 existingResponse="Replace" 將永遠使用 IIS 錯誤頁面,設為 PassThrough 則永遠使用程式輸出結果。Stackoverflow 上有一則詳細解說,值得參考。