Value types in web API models
If you use non-nullable value types (e.g., enums) in web API models, you probably aren't getting the behavior you are expecting, both for required and for optional properties. I find it better to always use nullable value types.
Let's say, you are using the following model for the body of a POST endpoint:
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Color
{
Red,
Green,
Blue
}
public class SampleRequest
{
[Required]
public Color Required { get; set; }
public Color Optional { get; set; }
}
Most likely, the intention for the Required
property was to cause a validation error if it isn't included in the request body. However, that's not what happens. Since enums are value types, they get initialized to their default value (Red
in the case of my Color
enum) even if a value isn't provided. This makes them pass the validation. So, even if I don't provide a value for the Required
property in the request, I get the same behavior as if I had set it to Red
. This makes the Required
attribute on a non-nullable value property useless.
The behavior is the same for the Optional
property, i.e., if it's not included in the request body, it will be initialized to the enum's default value. Since the Optional
property isn't meant to be required, such behavior isn't necessarily wrong. However, it's still impossible to distinguish between the case when the property wasn't included in the request and the case when it was set to the default value explicitly (i.e., Red
in this case). This might be fine if you want to fall back to this value when it's not provided, but not if you want to implement a different behavior for the two cases.
Because of all that, I prefer to always make value types in my model nullable:
public class SampleRequest
{
[Required]
public Color? Required { get; set; }
public Color? Optional { get; set; }
}
For the Required
property, this ensures a validation error when the property isn't included in the request, as one would expect for a property with a Required
attribute:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Required": [
"The Required field is required."
]
},
"traceId": "00-d5b0dbf509d308060b03f5be26c5b75e-108e983c46169713-00"
}
This will ensure that inside the action method it will always be set to a value, despite it being nullable. A null
value will not pass the validation.
The Optional
property will be set to null
when it isn't included in the request. This allows me to explicitly decide how to handle such a case. I can use the default value if I want to, but I can also handle the missing value differently if I want/need to.
You can find a sample project demonstrating the behavior in my GitHub repository. The latest commit uses nullable value types in the request. The one before that uses non-nullable value types. You can try the behavior for both approaches with the included .http
file or by running the tests.
Using non-nullable value types in web API models can be dangerous. If you are annotating them with the Required
attribute, you are definitely not getting the behavior you are expecting. But I'd argue that even for non-required properties, you are better off using nullable value types. However you decide, just make sure to thoroughly test your API to make sure its behavior matches your expectations and/or requirements.