noxi雑記

.NET、Angularまわりの小ネタブログ

ASP.NET Coreのバリデーションエラー自動応答

ASP.NET Core にはモデルをバリデーションする機能があります。 ASP.NET Core 2.1 では毎回記述していて面倒だったバリデーションエラーのレスポンスを返す処理を自動で返せるようになりました。 ASP.NET Core 2.2 ではこのレスポンス形式に変更が入ったようですので、バリデーションエラーの自動応答とレスポンスの変更点の2つを確認します。


バリデーションエラーの自動応答

まずは ASP.NET Core 2.1 で追加されたバリデーションエラーの自動応答を試してみます。バリデーションの自動応答とは、リクエストされた内容がバリデーション違反していたときに、今までだったらこのようなコードを記述していたところを ASP.NET Core フレームワークが自動でステータスコード 400 のレスポンスを返してくれる機能です。

[HttpPost]
public ActionResult Post([FromBody] string value)
{
    // ModelStateの状態をチェックして
    if (!this.ModelState.IsValid)
    {
        // バリデーション違反状態だったら400レスポンスを返す
        return this.BadRequest(this.ModelState);
    }

    return Ok();
}

バリデーションエラーの自動応答を利用するには、

  • Startup の ConfigureServices で MVC 追加時に SetCompatibilityVersion(CompatibilityVersion.Version_2_1) を設定する
  • ApiControllerAttribute をコントローラークラスに付与する

この2つの設定が必要です。 VisualStudio や Rider を使用して ASP.NET Core 2.1 のプロジェクトを新規で作成すると1つめの MVC 互換バージョン設定は既定で入っています。ですので、必要なのはコントローラーに ApiController 属性を付与することだけです。

MVC 互換バージョン設定

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

API 用のコントローラーを作成するには ControllerBase または Controller を継承したクラスを作成し、 API メソッドを定義します。 ControllerBaseController の違いは MVC の View 機能が実装されているかで、 ControllerBase には View の機能がありません。
GET の API でクエリストリングに対してバリデーションを設定するには次の様になります。

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    [HttpGet("sample")]
    public ActionResult GetSample([Required] string keyword, [Range(1, 10000)] int count = 1000)
    {
        return Ok();
    }
}

この API に対して api/values/sample?count=100000 でアクセスすると、必須バリデーションも数値のレンジバリデーションにも違反するため2つのバリデーションエラーが自動で応答されます。

{
  "count": [
    "The field count must be between 1 and 10000."
  ],
  "keyword": [
    "The keyword field is required."
  ]
}

なおこのレスポンスをコントローラーから直接返すには BadRequest を使用します。

[HttpGet("sample")]
public ActionResult GetSample([Required] string keyword, [Range(1, 10000)] int count = 1000)
{
    if (!this.ModelState.IsValid)
    {
        return this.BadRequest(this.ModelState);
    }
    ...
}

ASP.NET Core 2.2 でのレスポンス形式変更

先のコントローラーからのバリデーション自動応答を ASP.NET Core 2.2 環境で試すと次のような違う結果が返ってきます。

{
  "errors": {
    "count": [
      "The field count must be between 1 and 10000."
    ],
    "keyword": [
      "The keyword field is required."
    ]
  },
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "0HLJ9DK5VK4UQ:00000003"
}

直接エラーのオブジェクトが返ってくる形式から、エラータイトルやステータスコードまで含まれるようになり分かりやすくなりました。コントローラーから直接このモデルを返すには ValidationProblem を使用します。

[HttpGet("sample")]
public ActionResult GetSample([Required] string keyword, [Range(1, 10000)] int count = 1000)
{
    if (!this.ModelState.IsValid)
    {
        return this.ValidationProblem(this.ModelState);
    }
    ...
}

2.2 環境で自動応答のモデルを変更したくない場合は Startup.ConfigureServicesAPI の動作設定を変更し、 SuppressModelStateInvalidFiltertrue を設定します。

public void ConfigureServices(IServiceCollection services)
{
    services
        .AddMvc()
        .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
        .ConfigureApiBehaviorOptions(options =>
        {
            options.SuppressModelStateInvalidFilter = true;
        });
}