同事的專案遇到以下需求:依規格實作 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() 消失無蹤。

經爬文與實驗後,獲得以下心得:

  1. 使用 Response.SetStatus(HttpStatusCode.BadRequest) 會中止 Reponse,導致 return Content() 被無視。由 System.Web.WebPages.ResponseExtensions 原始碼可證實此點:
    public static void SetStatus(this HttpResponseBase response, int httpStatusCode)
    {
        response.StatusCode = httpStatusCode;
        response.End();
    }
  2. Response.StatusCode 屬性支援寫入,不用 SetStatus() 改成直接指定 StatusCode = 400 可避免 Response.End()。
  3. 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 上有一則詳細解說,值得參考。


Comments

# by 一個小路人

Oh my!剛好遇到這問題,因為在localHost是可以正常回傳json格式訊息的,但放到iis上就不行,顯示不出來 原來要設定TrySkipIisCustomErrors,感謝大大

Post a comment