.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