2

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://learn.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; }
    ....
  • You can add your own convention binding / validation metadata via `.AddMvc(o => o.ModelMetadataDetailsProviders.Add(...` (https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.mvcoptions.modelmetadatadetailsproviders?view=aspnetcore-3.1), but I'm not sure how to turn that into a good answer.... – Jeremy Lakeman Sep 21 '20 at 03:41
  • 3
    The `[Required]` attribute works with only nullable types. If you want to make sure that data for value types is present you may use the `[BindRequired]` attribute for them. – martikyan Sep 21 '20 at 07:22
  • Your question is a bit confusing - the title doesn't reflect the body. Besides, from .net core 3.0 non-nullable type properties are automatically considered having an implicit Required attribute? – atiyar Sep 21 '20 at 08:16
  • hi @atiyar can you show the resource saying all non-nullable are automatically have implicit Required? we were just testing in Net core 3.1, and were able to pass through with a missing class member in swagger/postman –  Sep 21 '20 at 15:53
  • 1
    @marksmith542 There you go - https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-3.1#required-attribute – atiyar Sep 21 '20 at 17:33
  • hi @atiyar strange, I dont have SuppressImplicitRequiredAttributeForNonNullableReferenceTypes anywhere in my program, I went to postman, totally removed a non-nullable class member from my json Object Data Request, and API is still trying to go through, using Net Core 3.1 –  Sep 21 '20 at 17:59
  • You can write a custom ModelBinder and use it once for a class. – sa-es-ir Sep 28 '20 at 18:53

1 Answers1

2

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...
            });
        }
    }
}
HMZ
  • 2,949
  • 1
  • 19
  • 30