|
using System.ComponentModel.DataAnnotations; |
|
using System.Globalization; |
|
|
|
namespace ComponentModel.DataAnnotations; |
|
|
|
/// <summary> |
|
/// Атрибут для сравнения значения свойства с другим свойством модели |
|
/// </summary> |
|
/// <remarks> |
|
/// Примеры использования: |
|
/// <code> |
|
/// [CompareTo(nameof(Age), Comparison.GreaterOrEqual)] |
|
/// [CompareTo(nameof(Password), Comparison.Equal, ErrorMessage = "Пароли не совпадают")] |
|
/// </code> |
|
/// </remarks> |
|
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] |
|
public sealed class CompareToAttribute : ValidationAttribute |
|
{ |
|
private readonly string _otherProperty; |
|
private readonly Comparison _comparison; |
|
|
|
/// <summary> |
|
/// Создает новый экземпляр атрибута сравнения |
|
/// </summary> |
|
/// <param name="otherProperty">Имя свойства для сравнения (используйте nameof())</param> |
|
/// <param name="comparison">Тип сравнения</param> |
|
public CompareToAttribute(string otherProperty, Comparison comparison) |
|
{ |
|
_otherProperty = otherProperty; |
|
_comparison = comparison; |
|
} |
|
|
|
/// <inheritdoc /> |
|
public override bool RequiresValidationContext => true; |
|
|
|
/// <inheritdoc /> |
|
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}"); |
|
} |
|
} |
|
|
|
/// <inheritdoc /> |
|
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("Неподдерживаемый тип сравнения") |
|
}; |
|
} |
|
|
|
/// <summary> |
|
/// Тип сравнения для атрибута CompareTo |
|
/// </summary> |
|
[Flags] |
|
public enum Comparison |
|
{ |
|
/// <summary>Меньше</summary> |
|
Less = 1 << 0, |
|
|
|
/// <summary>Равно</summary> |
|
Equal = 1 << 1, |
|
|
|
/// <summary>Больше</summary> |
|
Greater = 1 << 2, |
|
|
|
/// <summary>Не равно (Less | Greater)</summary> |
|
NotEqual = Less | Greater, |
|
|
|
/// <summary>Меньше или равно (Less | Equal)</summary> |
|
LessOrEqual = Less | Equal, |
|
|
|
/// <summary>Больше или равно (Greater | Equal)</summary> |
|
GreaterOrEqual = Greater | Equal |
|
} |
|
|