Enum as Required Field in ASP.NET Core WebAPI



The Problem

When creating a model for a WebApi project you sometimes have a need/want to use ENUMs as properties. Sometimes they can be nullable which is straight forward, but sometimes you want to ensure a value is passed in from the user. That’s what I’ll be discussing here.


What is Going On???

One day I created a simple model with an ENUM property that I expected to be required so I did what I usually did and added a `[Required]` attribute and kept moving thinking that would make sure if the client forgot to pass me a value they would get an error.

However, when they sent me a request with an empty value for the ENUM their query still succeeded. Then I started scratching my head… “Why was my required attribute not working???”, I asked myself and my team.

Turns out…

The problem faced here is nested in the order of operations of converting the body to JSON and validating the model validations.

First, .NET de-serializes the body into JSON with default values. This means if you pass in a null value for a non-nullable ENUM the default value will be set for it (the first item in the ENUM usually). Same thing happens for privatives.

Second, the ModelState gets validated, however, since the JSON parser already added a default value for the ENUM (or primitives) there is technically no error.

The Fix

To remedy this if I want an ENUM or PRIMATIVE to be “required” I do not use `[Required]`… I use `[JsonProperty(Required = Required.Always)]` which makes the property always required in the JSON body. So now when the body is de-serialized an error will be raised instead of using the default value for the ENUM or PRIMATIVE.

Next, I add the `[ValidEnum]` property to ensure the passed in value exists in the ENUM’s value list. So I use a combination of attributes for ENUMs:

[ValidEnum] — Verifies the enum exist
[JsonProperty(Required = Required.Always)]


public class ValidEnumAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, 
    ValidationContext validationContext)
    {
        if (value == null)
        {
            return ValidationResult.Success;
        }

        var type = value.GetType();

        if (!(type.IsEnum && Enum.IsDefined(type, value)))
        {
            return new ValidationResult(ErrorMessage ?? $"{value} is not a 
            valid value for type {type.Name}");
        }

        return ValidationResult.Success;
    }
}