Required properties and nullable reference types
When you enable nullable reference types for an existing Web API project, it changes how properties in request bodies are validated. This can easily break your application if you don't modify your models accordingly.
Let's say you have a POST endpoint with the following model for the request body:
public class SampleRequest
{
public string Optional { get; set; }
[Required]
public string Required { get; set; }
}
With nullable reference types disabled, the request will succeed when the Required
property is set, i.e., the request body is as follows:
{
"required": "required"
}
If the Required
property is not set, a 400 error response will be returned:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Optional": [
"The Required field is required."
]
},
"traceId": "00-22bd0455adefb441225c6d4de3c7b251-694837ae1d91dc60-00"
}
The value of the Optional
property doesn't affect the validation.
When you enable nullable reference types for the project, the validation behavior changes. If you send the following request body, which used to pass the validation:
{
"required": "required"
}
The request will now fail with the following response:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Optional": [
"The Optional field is required."
]
},
"traceId": "00-22bd0455adefb441225c6d4de3c7b251-694837ae1d91dc60-00"
}
As stated in the documentation, that's because:
The validation system treats non-nullable parameters or bound properties as if they had a
[Required(AllowEmptyStrings = true)]
attribute.
If you want to revert the behavior back to how it was with the nullable reference types disabled, you can set the following option when registering controllers with dependency injection in Program.cs
:
builder.Services.AddControllers(options =>
{
options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;
});
A more proper solution is to instead modify the model itself and correctly use the nullable reference types where appropriate. This also gets rid of the compiler warnings because the properties are not initialized:
public class SampleRequest
{
public string? Optional { get; set; }
public required string Required { get; set; }
}
Making the Optional
property nullable, makes the validation work as expected and also fixes the warning since a nullable property doesn't need to be initialized to a non-null value.
The required
modifier on the Required
property fixes the warning because it now requires the callers to initialize the value when instantiating the type, hence allowing the property to not be initialized in the class code. The Required
attribute is not needed anymore because it's now applied implicitly.
You can find a sample project demonstrating the behavior in my GitHub repository. The individual commits show step-by-step transition from having the nullable reference types disabled, over enabling them, then disabling the implicit Required
attributes and finally fixing the model. You can try out the behavior at each step either with the included .http
file or by running the tests.
Nullable reference types are a great addition to C#, but they work best in new projects. Enabling them in existing projects requires caution and can mean a lot of work. I have already written about the impact of nullable reference types on EF Core entities in the past. As you can see in this post, the ASP.NET Core models are impacted similarly.