using System.ComponentModel.DataAnnotations;
using System.Globalization;
namespace ComponentModel.DataAnnotations;
///
/// Атрибут для сравнения значения свойства с другим свойством модели
///
///
/// Примеры использования:
///
/// [CompareTo(nameof(Age), Comparison.GreaterOrEqual)]
/// [CompareTo(nameof(Password), Comparison.Equal, ErrorMessage = "Пароли не совпадают")]
///
///
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class CompareToAttribute : ValidationAttribute
{
private readonly string _otherProperty;
private readonly Comparison _comparison;
///
/// Создает новый экземпляр атрибута сравнения
///
/// Имя свойства для сравнения (используйте nameof())
/// Тип сравнения
public CompareToAttribute(string otherProperty, Comparison comparison)
{
_otherProperty = otherProperty;
_comparison = comparison;
}
///
public override bool RequiresValidationContext => true;
///
protected override ValidationResult? IsValid(
object? value,
ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(_otherProperty)
?? throw new InvalidOperationException($"Свойство {_otherProperty} не найдено");
var otherValue = property.GetValue(validationContext.ObjectInstance);
var comparison = (value, otherValue) switch
{
(null, null) => Comparison.Equal,
(null, _) => Comparison.Less,
(_, null) => Comparison.Greater,
_ when value is not IComparable =>
throw new InvalidOperationException(
$"Тип {value.GetType().Name} не поддерживает сравнение"),
_ => CompareValues(value, otherValue!)
};
return (_comparison & comparison) == comparison
? ValidationResult.Success
: new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
private static Comparison CompareValues(object value, object otherValue)
{
try
{
return ((IComparable)value).CompareTo(otherValue) switch
{
< 0 => Comparison.Less,
0 => Comparison.Equal,
_ => Comparison.Greater
};
}
catch (ArgumentException)
{
throw new InvalidOperationException(
$"Невозможно сравнить {value.GetType().Name} и {otherValue.GetType().Name}");
}
}
///
public override string FormatErrorMessage(string name) =>
!string.IsNullOrEmpty(ErrorMessage)
? string.Format(CultureInfo.CurrentCulture, ErrorMessage, name, _otherProperty)
: $"{name} должно быть {GetComparisonText()} {_otherProperty}.";
private string GetComparisonText() => _comparison switch
{
Comparison.Less => "меньше чем",
Comparison.Equal => "равно",
Comparison.Greater => "больше чем",
Comparison.NotEqual => "не равно",
Comparison.LessOrEqual => "меньше или равно",
Comparison.GreaterOrEqual => "больше или равно",
_ => throw new InvalidOperationException("Неподдерживаемый тип сравнения")
};
}
///
/// Тип сравнения для атрибута CompareTo
///
[Flags]
public enum Comparison
{
/// Меньше
Less = 1 << 0,
/// Равно
Equal = 1 << 1,
/// Больше
Greater = 1 << 2,
/// Не равно (Less | Greater)
NotEqual = Less | Greater,
/// Меньше или равно (Less | Equal)
LessOrEqual = Less | Equal,
/// Больше или равно (Greater | Equal)
GreaterOrEqual = Greater | Equal
}