Description
Consider this code:
[ApiController]
[Route("[controller]")]
public class FooController : ControllerBase
{
[HttpPost]
public ActionResult<Foo> Create(Foo foo)
{
return foo;
}
}
public class Foo
{
[Required]
public string Bar { get; set; }
}
If I send a request to this endpoint with no value for the Bar
property, I get the following response, as expected:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Bar": [
"The Bar field is required."
]
},
"traceId": "00-d8ae468fa137d578267ebd2ea54c9af5-993f06d39f4b827f-00"
}
But if I change Foo
to use the required
keyword instead of the [Required]
attribute:
public class Foo
{
public required string Bar { get; set; }
}
I get this response instead:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"$": [
"JSON deserialization for type 'Foo' was missing required properties including: 'bar'."
],
"foo": [
"The foo field is required."
]
},
"traceId": "00-0844d8c6645ea2073a7ee755d0204308-0ffe38d694f09b36-00"
}
This isn't very helpful.
First, the name foo
means nothing to the client, because it's just the name of the parameter in the controller action. It's not exposed in the OpenApi documentation or anywhere else. To the client, it's just the request body, it doesn't have a name.
Second, the response contains no error with bar
as the key to indicate that there's an error for this field.
Third, it's very surprising that the required
keyword doesn't have the same effect as the [Required]
attribute. I think I understand the reason (System.Text.Json takes the required
keyword into account when deserializing, so if the property is missing, it just throws a deserialization exception, so there's never a chance to actually validate the deserialized model), but it's not a great experience.
The workaround is easy (don't use the required
keyword, use the [Required]
attribute instead), but it's not great, because now I have a nullability warning on my property, and I need to initialize it with a dummy value to fix it. I like the keyword because it's a clean way to suppress the warning for non-nullable properties, but it completely breaks this scenario.
I'm not sure if it's actually a bug, or just a known limitation, but it's annoying, and it would be nice to fix it. Is there maybe a way to make the JSON serializer ignore the required
keyword, so that model validation can do its work? (I couldn't find it).
Sorry if that issue has already been reported. I searched for it but couldn't find anything, which I found surprising, because I'm probably not the first one to notice this.