'DotNet 6 - Multiple property Attributes not properly Validated

since I moved my project to dotnet 6 my properties Attribute are not properly validated.
I have a MultipleAttributeUsage Class below with 2 Compare Attributes on Middle Property.
With dotNet 5 both are validated.
When debugging on DotNet 5.0 there are going throw IsValid() in my custom CompareAttribute class but with DotNet 6 it goes only once for the last property.
Does anyone have already seeing this issue ?

private class MultipleAttributeUsage
{
    public int Min { get; set; }

    [Compare(ComparisonOperator.Greater, nameof(Min))]
    [Compare(ComparisonOperator.Less, nameof(Max))]
    public int Middle { get; set; }

    public int Max { get; set; }
}

And here My CompareAttribute class

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Reflection;

namespace xxxxx
{
    public enum ComparisonOperator
    {
        Less,
        LessOrEqual,
        Equal,
        Greater,
        GreaterOrEqual,
        NotEqual
    }
    
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    public class CompareAttribute : ValidationAttribute
    {
        private readonly IReadOnlyDictionary<ComparisonOperator, Predicate<int>> _operatorEvaluatorMap =
            new ReadOnlyDictionary<ComparisonOperator, Predicate<int>>(
                new Dictionary<ComparisonOperator, Predicate<int>>
                {
                    {ComparisonOperator.Less, r => r < 0},
                    {ComparisonOperator.LessOrEqual, r => r <= 0},
                    {ComparisonOperator.Equal, r => r == 0},
                    {ComparisonOperator.Greater, r => r > 0},
                    {ComparisonOperator.GreaterOrEqual, r => r >= 0},
                    {ComparisonOperator.NotEqual, r => r != 0}
                });


        private readonly IReadOnlyDictionary<ComparisonOperator, string> _comparisonWordsMap =
            new ReadOnlyDictionary<ComparisonOperator, string>(
                new Dictionary<ComparisonOperator, string>
                {
                    {ComparisonOperator.Less, ValidationConstants.Less},
                    {ComparisonOperator.LessOrEqual, ValidationConstants.LessOrEqual},
                    {ComparisonOperator.Equal, ValidationConstants.Equal},
                    {ComparisonOperator.Greater, ValidationConstants.Greater},
                    {ComparisonOperator.GreaterOrEqual, ValidationConstants.GreaterOrEqual},
                    {ComparisonOperator.NotEqual, ValidationConstants.NotEqual}
                });

        /// <summary>
        /// Creates the instance of <see cref="CompareAttribute"/> class.
        /// </summary>
        /// <param name="comparisonOperator">The comparision operator. See <see cref="ComparisonOperator"/> for all possible values.</param>
        /// <param name="otherProperty">The property name to compare with.</param>
        public CompareAttribute(ComparisonOperator comparisonOperator, string otherProperty)
        {
            if (string.IsNullOrWhiteSpace(otherProperty))
            {
                throw new ArgumentNullException(nameof(otherProperty));
            }

            if (!_operatorEvaluatorMap.ContainsKey(comparisonOperator) ||
                !_comparisonWordsMap.ContainsKey(comparisonOperator))
            {
                throw new ArgumentException(ValidationConstants.OperatorIsNotSupported, nameof(comparisonOperator));
            }

            OtherProperty = otherProperty;
            ComparisonOperator = comparisonOperator;
        }

        public string OtherProperty { get; }

        public ComparisonOperator ComparisonOperator { get; }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            // don't validate if this value is null or default value for the type
            if (IsNullOrDefaultForType(value))
            {
                return ValidationResult.Success;
            }

            var valueComparable = VerifyIsComparable(value, validationContext.MemberName);

            var otherProperty = GetOtherProperty(validationContext.ObjectType);
            var otherValue = otherProperty.GetValue(validationContext.ObjectInstance);

            // don't validate if other value is null or default value for the type
            if (IsNullOrDefaultForType(otherValue))
            {
                return ValidationResult.Success;
            }

            var otherValueComparable = VerifyIsComparable(otherValue, OtherProperty);

            var comparisonResult = valueComparable.CompareTo(otherValueComparable);
            var comparisonResultEvaluator = _operatorEvaluatorMap[ComparisonOperator];

            return comparisonResultEvaluator(comparisonResult)
                ? ValidationResult.Success
                : BuildValidationFailResult(validationContext.MemberName);
        }

        private bool IsNullOrDefaultForType(object value)
        {
            if (value == null)
            {
                return true;
            }

            var type = value.GetType();

            if (type.IsValueType)
            {
                // for nullable types check for underlying type default
                var defaultInstance = Activator.CreateInstance(Nullable.GetUnderlyingType(type) ?? type);
                return value.Equals(defaultInstance);
            }
            else
            {
                return false;
            }
        }

        private IComparable VerifyIsComparable(object value, string propertyName)
        {
            return value is IComparable comparable
                ? comparable
                : throw new ArgumentException(ValidationConstants.ShouldBeComparableMessage, propertyName);
        }

        private PropertyInfo GetOtherProperty(Type objectType)
        {
            var otherProperty = objectType.GetProperty(OtherProperty);
            return otherProperty ??
                   throw new ArgumentException(ValidationConstants.CannotFindPropertyToCompare, OtherProperty);
        }

        private ValidationResult BuildValidationFailResult(string thisPropertyName)
        {
            return new ValidationResult(GetComparisonValidationMessage(thisPropertyName), new[] {thisPropertyName});
        }

        private string GetComparisonValidationMessage(string thisPropertyName)
        {
            if (!string.IsNullOrWhiteSpace(ErrorMessage))
                return ErrorMessage;

            var comparisonWord = _comparisonWordsMap[ComparisonOperator];

            return string.Format(ValidationConstants.CompareAttributeComparisonValidationMessage,
                thisPropertyName, comparisonWord, GetComparisonAdverb(), OtherProperty);
        }

        private string GetComparisonAdverb()
        {
            switch (ComparisonOperator)
            {
                case ComparisonOperator.LessOrEqual:
                case ComparisonOperator.Equal:
                case ComparisonOperator.GreaterOrEqual:
                case ComparisonOperator.NotEqual:
                {
                    return ValidationConstants.ToAdverb;
                }
                case ComparisonOperator.Less:
                case ComparisonOperator.Greater:
                {
                    return ValidationConstants.ThanAdverb;
                }
            }

            return string.Empty;
        }
    }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source