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 }