Skip to content

ModelState validation changes when adding a new property to an existing model #41687

@bkoelman

Description

@bkoelman

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Adding a new property to an existing model changes the required-ness of another existing (unmodified) property when nullable reference types are enabled. As a result, the outcome of ModelState validation changes.

Expected Behavior

Adding a new property should not change the validation of another existing (unmodified) property.

Steps To Reproduce

  • In Visual Studio 2022, create a new API project: File > New > Project > ASP.NET Core Web API (accept defaults).
  • Add the following code to a new file in the project:
    public abstract class Identifiable<TId>
    {
        public virtual TId Id { get; set; } = default!;
    
        //public string? Version { get; set; }
    }
    
    public class Customer : Identifiable<string?>
    {
        public string Name { get; set; } = null!;
    
        [Range(1, 150)]
        public int Age { get; set; }
    }
  • Add the next method to Controllers\WeatherForecastController.cs:
    [HttpPost]
    public virtual IActionResult Post([FromBody] Customer customer)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }
    
        return Ok($"Hello '{customer.Name}' with ID '{customer.Id}'.");
    }
  • Run the application.
  • From the Swagger UI in the browser, expand "POST WeatherForecast" and click Try it out. Replace the request body with {} and click Execute.
  • Note the response contains two errors:
      "errors": {
        "Age": [
          "The field Age must be between 1 and 150."
        ],
        "Name": [
          "The Name field is required."
        ]
      }
  • Stop the application and uncomment the Version property from the first code snippet.
  • Run the application and execute the same POST request from Swagger UI in the browser.
  • Note the response now contains three errors:
      "errors": {
        "Id": [
          "The Id field is required."
        ],
        "Age": [
          "The field Age must be between 1 and 150."
        ],
        "Name": [
          "The Name field is required."
        ]
      }

Exceptions (if any)

No response

.NET Version

6.0.203

Anything else?

I traced this down to DataAnnotationsMetadataProvider. Uncommenting the Version property makes the C# compiler switch from [NullableAttribute] to [NullableContextAttribute], causing the metadata provider to bail out early due to the use of generics here (or gets it wrong here, I don't remember exactly).

This appears to be fixed in #39219. So I've tested with .NET 7 Preview4 and can confirm the problem no longer occurs there.

Feel free to close this issue, as the problem is already solved. I just thought it would be good to log this for awareness and traceability.

I don't think it makes sense to backport this to .NET 6, because NullabilityInfoContext doesn't behave correctly in the .NET 6 runtime version:

var property = typeof(Customer).GetProperty("Id")!;

var nullContext = new NullabilityInfoContext();
var nullability = nullContext.Create(property);
var isOptional = nullability.ReadState != NullabilityState.NotNull;

// .NET 6 v6.0.300:
// class Customer : Identifiable<string> => isOptional: true
// class Customer : Identifiable<string?> => isOptional: true

// .NET 7 Preview 4:
// class Customer : Identifiable<string> => isOptional: false
// class Customer : Identifiable<string?> => isOptional: true

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions