Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9707

FromBody のモデル検証が .NET Core と .NET Framework で違う

$
0
0

.NET Core の場合はデフォルトでモデルの検証処理が入っています。 .NET Framework の場合は入っていません。 .NET Framework から .NET Core に移行するときなどに注意する必要があります。

.NET 環境

どちらも Web API を使用します。

  • .NET Core 3.1
  • .NET Framework 4.7.2

サンプルコード

それぞれで同じものを使えるモデルクラスと、今回の比較対象の Web API Post Method のサンプルコードです。

// Request Body に使用するモデルクラスpublicclassModel{[Required]publicstringa{get;set;}}
// .NET Core// Request されてきた値を JSON 形式で返します。[HttpPost]publicstringPost([FromBody]Modelvalue){returnJsonSerializer.Serialize(value);}
// .NET Framework// こちらも同じように Request されてきた値を JSON 形式で返します。publicstringPost([FromBody]Modelvalue){returnJsonConvert.SerializeObject(value);}

動作確認

いくつか検証エラーになりそうなパターンで Shell から Request してみると違いがわかります。

Body に何も設定しない場合

.NET Core の場合はステータスコード 400 と RFC 7807 仕様に準拠したエラーの内容が返ってきます。 .NET Framework の場合はステータスコード 200 と "null" が返ってきます。

# .NET Core$ curl --location--request POST 'https://localhost:44370/api/home'--header'Content-Type: application/json'--data-raw''-k# {#   "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",#   "title": "One or more validation errors occurred.",#   "status": 400,#   "traceId": "|cebcea05-40c27a10f8cc8ff5.",#   "errors": {#     "": [#       "A non-empty request body is required."#     ]#   }# }
# .NET Framework$ curl --location--request POST 'https://localhost:44304/api/home'--header'Content-Type: application/json'--data-raw''-k# "null"

Body に {} を設定した場合

何も設定しない場合と同じステータスコードが返ってきます。エラーの内容は変わっていて、モデルクラスの Required エラーになっています。

# .NET Core$ curl --location--request POST 'https://localhost:44370/api/home'--header'Content-Type: application/json'--data-raw'{}'-k# {#   "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",#   "title": "One or more validation errors occurred.",#   "status": 400,#   "traceId": "|cebcea05-40c27a10f8cc8ff5.",#   "errors": {#       "a": [#           "The a field is required."#       ]#   }# }
# .NET Framework$ curl --location--request POST 'https://localhost:44304/api/home'--header'Content-Type: application/json'--data-raw'{}'-k# {\"a\":null}

Body の a に null を設定した場合

先程試した {} を設定した場合と同じステータスコードとエラー内容が返ってきます。

# .NET Core$ curl --location--request POST 'https://localhost:44370/api/home'--header'Content-Type: application/json'--data-raw'{"a": null}'-k# {#   "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",#   "title": "One or more validation errors occurred.",#   "status": 400,#   "traceId": "|cebcea05-40c27a10f8cc8ff5.",#   "errors": {#       "a": [#           "The a field is required."#       ]#   }# }
# .NET Framework$ curl --location--request POST 'https://localhost:44304/api/home'--header'Content-Type: application/json'--data-raw'{"a": null}'-k# {\"a\":null}

まとめ。なんで動作が違うのか

.NET Core には ApiController に対して ModelStateInvalidFilter がデフォルト動作するようになっています。
これによって .NET Framework では自分で用意する必要があった ModelState.IsValid と null のチェックが実装不要になっています。
僕たち開発者にとっては .NET Core のほうが検証処理をデフォルト実装していて便利だと思います。恐らくそういう意見が多くて .NET Core に実装されたのだと考えています。

お互いの動作を合わせるようなコードも載せておきます。細かい複雑な部分など完全な一致ではないので、参考程度にしてください。

.NET Core のデフォルト検証を外したい場合のコード

Startup.cs で options.SuppressModelStateInvalidFilter = trueを設定すれば検証しなくなります。

// Startup.cspublicvoidConfigureServices(IServiceCollectionservices){services.AddControllers().ConfigureApiBehaviorOptions(options=>{options.SuppressModelStateInvalidFilter=true;});}

.NET Framework で FromBody のデフォルト検証を追加したい場合のコード

ActionFilterAttribute を継承した ModelState と null のチェックをするフィルタークラスを自作して、 WebApiConfig のフィルターに追加します。

// FromBody のリクエストパラメーターが null または ModelState.IsValid == false の場合にステータスコード 400 を返すフィルタークラス// Note: もっと効率のよい実装や、わかりやすい実装があったら教えてください。publicclassModelStateInvalidFilter:ActionFilterAttribute{publicoverridevoidOnActionExecuting(HttpActionContextactionContext){varfromBodyParameterNames=actionContext.ActionDescriptor.GetParameters().Where(p=>p.ParameterBinderAttribute!=null&&p.ParameterBinderAttribute.Match(newFromBodyAttribute())).Select(descriptor=>descriptor.ParameterName);foreach(varfromBodyParameterNameinfromBodyParameterNames){foreach(varrequiredArgumentinactionContext.ActionArguments.Where(pair=>pair.Key==fromBodyParameterName)){if(requiredArgument.Value==null){actionContext.Response=actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,"A non-empty request body is required.");}}}if(!actionContext.ModelState.IsValid){actionContext.Response=actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest,actionContext.ModelState);}}}
// WebApiConfig.cspublicstaticclassWebApiConfig{publicstaticvoidRegister(HttpConfigurationconfig){// Web API configuration and servicesconfig.Filters.Add(newModelStateInvalidFilter());// 以下、略 ...}}

参考文献

ASP.NET Core を使って Web API を作成する | Microsoft Docs

aspnetcore/ModelStateInvalidFilter.cs at master · dotnet/aspnetcore · GitHub


Viewing all articles
Browse latest Browse all 9707

Trending Articles