'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 |
|---|
