The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

使用 ASP.NET Core 2.2 開發 Web API 的注意事項

最近整理了一下 ASP.NET Core 2.2 Web API 在開發時的注意事項,魔鬼總是出現在細節裡,有些資訊沒遇到問題也不會特別去看,但有時間的時候,從頭到尾釐清一遍,其實還是很有幫助的。

選用正確的基底類別

  • ASP.NET Core Web API
    • 請務必繼承自 ControllerBase 抽象類別
    • ControllerBase 提供不少開發 Web API 所需的輔助方法
  • ASP.NET Core MVC
    • 請務必繼承自 Controller 類別
    • Controller 繼承自 ControllerBase,但額外提供許多 Views 相關功能與輔助方法
  • 混合式控制器 (Mixed Controller) (不建議)
    • 如果你要在一個控制器混用 MVC 與 Web API 的話,請繼承 Controller 類別

ASP.NET Core Web API 常見的 Attributes 說明

  • 宣告 Controller/Action 回應狀態碼
  • 宣告 Controller/Action 回應內容類型 (Content-Type)
  • 宣告 Controller/Action 可接受內容類型 (Accept)
  • 宣告 Action 可接受HTTP 動詞路由範本(route template)
  • 宣告 Controller 屬性路由預設前置詞 (Route Prefix)
  • 宣告 Controller 啟用常見的 API 特性 (API-specific behaviors)
  • 套用 [ApiController] 屬性之後,將會啟用以下特性:
    • 必須使用屬性路由(Attribute Routing)

    • 只要發生模型驗證失敗,就會自動回應 HTTP 400 (Bad Request),並且以 ValidationProblemDetails 型別回應,預設的 JSON 結構如下:

      {
          "errors": {
              "name": [
                  "The name field is required."
              ]
          },
          "title": "One or more validation errors occurred.",
          "status": 400,
          "traceId": "80000093-0002-fe00-b63f-84710c7967bb"
      }
      

      這也意味著以下程式碼可以完全從每一個 Action 中移除:

      if (!ModelState.IsValid)
      {
          return BadRequest(ModelState);
      }
      

      你可以關閉驗證失敗時自動 HTTP 400 回應

      services.AddMvc()
          .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
          .ConfigureApiBehaviorOptions(options =>
          {
              // 關閉驗證失敗時自動 HTTP 400 回應
              options.SuppressModelStateInvalidFilter = true;
          });
      

      也可以自訂驗證錯誤時的回應訊息

      services.AddMvc()
         .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
         .ConfigureApiBehaviorOptions(options =>
         {
            options.InvalidModelStateResponseFactory = context =>
            {
               var problemDetails = new ValidationProblemDetails(context.ModelState)
               {
                  Type = "https://contoso.com/probs/modelvalidation",
                  Title = "One or more model validation errors occurred.",
                  Status = StatusCodes.Status400BadRequest,
                  Detail = "See the errors property for details.",
                  Instance = context.HttpContext.Request.Path
               };
      
               return new BadRequestObjectResult(problemDetails)
               {
                  ContentTypes = { "application/problem+json" }
               };
            };
          });
      

      如果你想在 InvalidModelStateResponseFactory 工廠方法中寫些 Log 訊息,可以參考 How to log automatic 400 responses on model validation errors 這篇討論中提供的範例程式。

    • 繫結 Action 參數時會自動套用模型繫結的預設規則

      • 複雜型別預設就會自動套用 [FromBody] 屬性,但有些特殊目的的型別是例外,例如:IFormCollection and CancellationToken
      • 參數型別如果是 IFormFileIFormFileCollection 的話,會自動套用 [FromForm] 屬性
      • 任何參數名稱如果剛好等於路由範本(route template)的路由參數名稱,就會自動套用 [FromRoute] 屬性
      • 簡單模型任何其他參數,全部都會自動套用 [FromQuery] 屬性
      • 任何預設規則外的模型繫結,都需要明確指定以下任何一個屬性:
        • [FromBody] - 從 Request body 取值
        • [FromForm] - 從 Request body 中的表單資料取值
        • [FromHeader] - 從 Request header 取值
        • [FromQuery] - 從 Query String 取值
        • [FromRoute] - 從目前要求的路由變數取值
        • [FromServices] - 從 DI 容器中取得服務物件
    • 當設定為 CompatibilityVersion.Version_2_2 相容模式後,你可以在組件層級套用 [ApiController] 屬性,如下範例:

      public void ConfigureServices(IServiceCollection services)
      {
          services.AddMvc()
              .SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
      }
      
      [assembly: ApiController]
      namespace WebApiSample
      {
          public class Startup
          {
              ...
          }
      }
      

全新的 HTTP 錯誤回應模型

如果你將 MVC 相容層級設定為 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2) 的話,預設所有的錯誤都會遵照 RFC 7807 specification 的規定進行回應,從 ASP.NET Core 2.1 開始建立了一個 ProblemDetails 模型類別,專門用來描述各種不同的 HTTP API 錯誤資訊。

從 ASP.NET Core 2.2 開始,ASP.NET Core 才正式將所有錯誤回應改用 ProblemDetails 模型類別輸出!如果你想維持以前的預設訊息格式 (HttpError),可以將 相容模式 調整為 CompatibilityVersion.Version_2_1,如下範例:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

當你的 Action 程式這樣寫:

[HttpGet("{id}")]
public IActionResult Get(int id)
{
    return NotFound();
}

當回應 HTTP 404 的時候,其 Response Body 內容如下:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
  "title": "Not Found",
  "status": 404,
  "traceId": "8000001c-0000-f900-b63f-84710c7967bb"
}

這個問題回應的預設訊息內容是可以自定的,你可以透過調整 ClientErrorMapping 屬性來改變這個內容,請參考以下 ConfigureServices() 中的設定:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = false;
        options.SuppressInferBindingSourcesForParameters = false;
        options.SuppressModelStateInvalidFilter = false;

        // 設定 Client Errors (4xx) 的 Title 與 Link 內容
        options.SuppressMapClientErrors = false;
        options.ClientErrorMapping[404].Title = "找不到網頁";
        options.ClientErrorMapping[404].Link = "https://httpstatuses.com/404";
    });

設定完後,其回應內容如下:

{
  "type": "https://httpstatuses.com/404",
  "title": "找不到網頁",
  "status": 404,
  "traceId": "80000044-0001-fb00-b63f-84710c7967bb"
}

如果你想要完全關閉用戶端錯誤(Client Errors)的任何訊息,則可以做出以下調整:

services.AddMvc()
    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressMapClientErrors = true;
    });

相關連結

留言評論