Skip to content

Model validation doesn't work well for properties with the required keyword #61511

Closed
@thomaslevesque

Description

@thomaslevesque

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesfeature-validationIssues related to model validation in minimal and controller-based APIs

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions