System.ComponentModel.DataAnnotations.Required validation doesn't work how most people imagine it working. Required - it does have value, if the property is nullable and value is null - this validation will fail. However, if we have value type property - then we always have value, and required validation never fails.

Data annotation validation is invoked after model binder, which means that if property is value type will be created with default value and required validation never fails.

Model binder creates an empty model and assigns values from JSON. If a JSON value is missing for enum or integer - we will get default value, and sometimes this is not desired behavior. We can approach this problem from two sides, we can make value types nullable or we can use JSON validation and fail inside the model binder.

Lets explore these two validation types by creating small project, start with empty ASP.NET core API project.

Validation with System.ComponentModel.DataAnnotations

using System.ComponentModel.DataAnnotations;

namespace RequiredValidation
{
    public class WeatherModel
    {
        [Required]
        public int Temperature { get; set; }

        [Required]
        public string Title { get; set; }
    }
}

Validation with NewtonsoftJson

 Microsoft.AspNetCore.Mvc.NewtonsoftJson

    public void ConfigureServices(IServiceCollection services)
    {

        services
            .AddControllers()
            .AddNewtonsoftJson();

using Newtonsoft.Json;

namespace RequiredValidation
{
    public class Weather2Model
    {
        [JsonProperty(Required = Required.Always)]
        public int Temperature { get; set; }

        [JsonProperty(Required = Required.Always)]
        public string Title { get; set; }
    }
}

Endpoints

Controller endpoints in both cases are identical, no additional changes are needed. To make testing easier I just return same model.

        [HttpPost]
        [Route("Update")]
        public IActionResult UpdateWeather(WeatherModel weather)
        {
            return Ok(weather);
        }

        [HttpPost]
        [Route("Update2")]
        public IActionResult UpdateWeather2(Weather2Model weather)
        {
            return Ok(weather);
        }

Testing DataAnnotations validation

Lets start by testing DataAnnotations validation "Update" endpoint.


If we ignore value type property, it will get default value.


If we ignore string property with required attribute, we get error message, as expected.

Testing JSON property validation


We get error message when integer is not present in request.


Same as above, when string is not present in request we get error message.

Summary

Back to C# basics, value types always have a default value - unless they are nullable. Adding required on value type property doesn't make sense since it will be with default value either it's zero or first enum value.