ASP.NET Coreでリクエストパラメータにrecordを使った場合に出力されるswagger.jsonにRequired等の属性が反映されない問題があったのでまとめました。 問題になっているのはだいたい次のIssueと同じ内容です。
最近はエンドポイントのリクエストパラメータにrecordを使うことが多く、次のようなパラメータを作ってASP.NET Coreのエンドポイントで受け取っています。
public record TodoCreateParameters([Required] string Description, [Required] DateTime DateTime)
これを次のようなアクションで受け取ります。
[HttpPost] public IActionResult CreateTodo([FromBody] TodoCreateParameters parameters) { // 永続化層への登録処理などreturn Created(); }
これでデバッグ実行してエンドポイントを叩いてみると、パラメータがnullのときにバリデーションが効いて400BadRequestが返りうまくいっているように見えるのですが、 swagger.jsonを確認してみると、
"components": {"schemas": {"TodoCreateParameters": {"type": "object", "properties": {"description": {"type": "string", "nullable": true}, "dateTime": {"type": "string", "format": "date-time" }}, "additionalProperties": false}}}
TodoCreateParametersのdescriptionとdateTimeがrequiredになっていません。
なので属性のAttributeTargetsをpropertyと明示するようにしました。
public record TodoCreateParameters([property: Required] string Description, [property: Required] DateTime DateTime)
すると生成されるswagger.jsonはrequiredがつくようになりました。
"components": {"schemas": {"TodoCreateParameters": {"required": ["dateTime", "description" ], "type": "object", "properties": {"description": {"minLength": 1, "type": "string" }, "dateTime": {"type": "string", "format": "date-time" }}, "additionalProperties": false}}}
ただ、ここでエンドポイントを叩いてみるとエラーになります。
System.InvalidOperationException: Record type 'PrimaryCtorValidationSample.TodoCreateParameters' has validation metadata defined on property 'DateTime' that will be ignored. 'DateTime' is a parameter in the record primary constructor and validation metadata must be associated with the constructor parameter.
プライマリコンストラクタの引数はコンストラクタの引数とプロパティを兼ねているので、どっちにつける属性なのかハッキリしてほしいみたいな内容だと思います。 エラーメッセージに従って、[property: Required]を[param: Required]に変えてみました。すると今度は実行時エラーは出なくなりましたがswagger.jsonにrequiredが反映されなくなりました…。
良い解決策が見つからず妥協して次のようにrecordを作りました。
public record TodoCreateParameters { public TodoCreateParameters(string description, DateTime dateTime) { Description = description; DateTime = dateTime; } [Required] publicstring Description { get; init; } [Required] public DateTime DateTime { get; init; } }
プロパティとコンストラクタが簡潔に書けるrecordの良いところが無くなってますがこれでやりたいことはできるようになりました。
ただこれだとReSharperがプライマリーコンストラクタを使うように波線を出して提案してくるのが少しウザいです…。