.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 に使用するモデルクラス
public class Model
{
[Required]
public string a { get; set; }
}
// .NET Core
// Request されてきた値を JSON 形式で返します。
[HttpPost]
public string Post([FromBody]Model value)
{
return JsonSerializer.Serialize(value);
}
// .NET Framework
// こちらも同じように Request されてきた値を JSON 形式で返します。
public string Post([FromBody]Model value)
{
return JsonConvert.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.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
}
.NET Framework で FromBody のデフォルト検証を追加したい場合のコード
ActionFilterAttribute を継承した ModelState と null のチェックをするフィルタークラスを自作して、 WebApiConfig のフィルターに追加します。
// FromBody のリクエストパラメーターが null または ModelState.IsValid == false の場合にステータスコード 400 を返すフィルタークラス
// Note: もっと効率のよい実装や、わかりやすい実装があったら教えてください。
public class ModelStateInvalidFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var fromBodyParameterNames = actionContext.ActionDescriptor.GetParameters()
.Where(p => p.ParameterBinderAttribute != null && p.ParameterBinderAttribute.Match(new FromBodyAttribute()))
.Select(descriptor => descriptor.ParameterName);
foreach (var fromBodyParameterName in fromBodyParameterNames)
{
foreach (var requiredArgument in actionContext.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.cs
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.Filters.Add(new ModelStateInvalidFilter());
// 以下、略 ...
}
}
参考文献
ASP.NET Core を使って Web API を作成する | Microsoft Docs
aspnetcore/ModelStateInvalidFilter.cs at master · dotnet/aspnetcore · GitHub