Net Core: Enforce Required Class Members in Request API Automatically, for Not Null Data Types

Viewed 649

We have API Controller which obtains Product Data with Request Dto. To make class member required, we need to place Required Attribute in the Dto. Otherwise service can execute with empty member.

https://docs.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-3.1

Currently have Request DTO with 20+ members, and writing [Required] in all class members seems very repetitive.

My question is, why is RequiredAttribute necessary? We already have int and nullable members. Shouldn't data types themselves enforce the contract?

And Is there a way to automate and have class members enforce Required, without writing everywhere ?

[HttpPost]
[Route("[action]")]
public async Task<ActionResult<ProductResponse>> GetProductData(BaseRequest<ProductRequestDto> request)
{
    var response = await _productService.GetProductItem(request);
    return Ok(response);


public class ProductRequestDto
{
    public int ProductId { get; set; }
    public bool Available { get; set; }
    public int BarCodeNumber{ get; set; }
    ....

How to add custom error message with “required” htmlattribute to mvc 5 razor view text input editor

public class ProductRequestDto
{
    [Required]
    public int ProductId { get; set; }

    [Required]
    public bool Available { get; set; }

    [Required]
    public int BarCodeNumber{ get; set; }
    ....
1 Answers

If you're open to using Fluent Validation you can do something like this:

This validator can validate any dto using reflection

Validator:

using FluentValidation;
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleTests
{
    public class MyValidator : AbstractValidator<ModelDTO>
    {
        public MyValidator()
        {
            foreach (var item in typeof(ModelDTO).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance))
            {
                Console.WriteLine($"Name: {item.Name}, Type: {item.PropertyType}");

                if (item.PropertyType == typeof(int))
                {
                    RuleFor(x => (int)item.GetValue(x, null)).NotEmpty<ModelDTO,int>();
                }
                else
                {
                    RuleFor(x => (string)item.GetValue(x, null)).NotEmpty<ModelDTO, string>();
                }
            //Other stuff...
            }
        }
    }
}

The NotEmpty refuses null and default values.

Program.cs

using System;
using System.ComponentModel.DataAnnotations;
using System.IO;

namespace ConsoleTests
{
    class Program
    {
        static void Main(string[] args)
        {

            try
            {
                ModelDTO modelDTO = new ModelDTO
                {
                    MyProperty1 = null, //string
                    MyProperty2 = 0, //int
                    MyProperty3 = null, //string
                    MyProperty4 = 0 //int
                };

                MyValidator validationRules = new MyValidator();
                FluentValidation.Results.ValidationResult result = validationRules.Validate(modelDTO);

                foreach (var error in result.Errors)
                {
                    Console.WriteLine(error);
                }

                Console.WriteLine("Done!");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
}

Output:

Name: MyProperty1, Type: System.String
Name: MyProperty2, Type: System.Int32
Name: MyProperty3, Type: System.String
Name: MyProperty4, Type: System.Int32
must not be empty.
must not be empty.
must not be empty.
must not be empty.
Done!

And for a more complete solution: See this SO question how-to-use-reflection-in-fluentvalidation.

It uses the same concept, the validator takes a PropertyInfo and does its validation by getting the type and value.

The below code is from the mentioned question.

Property Validator:

public class CustomNotEmpty<T> : PropertyValidator
{
    private PropertyInfo _propertyInfo;

    public CustomNotEmpty(PropertyInfo propertyInfo)
        : base(string.Format("{0} is required", propertyInfo.Name))
    {
        _propertyInfo = propertyInfo;
    }

    protected override bool IsValid(PropertyValidatorContext context)
    {
        return !IsNullOrEmpty(_propertyInfo, (T)context.Instance);
    }

    private bool IsNullOrEmpty(PropertyInfo property, T obj)
    {
        var t = property.PropertyType;
        var v = property.GetValue(obj);

        // Omitted for clarity...
    }
}

Rule Builder:

public static class ValidatorExtensions
{
    public static IRuleBuilderOptions<T, T> CustomNotEmpty<T>(
        this IRuleBuilder<T, T> ruleBuilder, PropertyInfo propertyInfo)
    {
        return ruleBuilder.SetValidator(new CustomNotEmpty<T>(propertyInfo));
    }
}

And finally the validator:

public class FooValidator : AbstractValidator<Foo>
{
    public FooValidator(Foo obj)
    {
        // Iterate properties using reflection
        var properties = ReflectionHelper.GetShallowPropertiesInfo(obj);//This is a custom helper that retrieves the type properties.
        foreach (var prop in properties)
        {
            // Create rule for each property, based on some data coming from other service...
            RuleFor(o => o)
                .CustomNotEmpty(obj.GetType().GetProperty(prop.Name))
                .When(o =>
            {
                return true; // do other stuff...
            });
        }
    }
}
Related