Пользовательские атрибуты проверки: сравнение двух свойств в одной и той же модели
Есть ли способ создать настраиваемый атрибут в ядре ASP.NET для проверки того, является ли одно свойство date меньше, чем другое свойство даты в модели, используя ValidationAttribute
.
Предположим, у меня есть это:
public class MyViewModel
{
[Required]
[CompareDates]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; } = DateTime.Parse("3000-01-01");
}
Я пытаюсь использовать что-то вроде этого:
public class CompareDates : ValidationAttribute
{
public CompareDates()
: base("") { }
public override bool IsValid(object value)
{
return base.IsValid(value);
}
}
Я нашел другое сообщение SO, которое предлагает использовать другую библиотеку, но я предпочитаю придерживаться ValidationAttribute, если это выполнимо.
Ответы
Ответ 1
Вы можете создать собственный атрибут проверки для сравнения двух свойств. Это проверка на стороне сервера:
public class MyViewModel
{
[DateLessThan("End", ErrorMessage = "Not valid")]
public DateTime Begin { get; set; }
public DateTime End { get; set; }
}
public class DateLessThanAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public DateLessThanAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
var currentValue = (DateTime)value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
throw new ArgumentException("Property with this name not found");
var comparisonValue = (DateTime)property.GetValue(validationContext.ObjectInstance);
if (currentValue > comparisonValue)
return new ValidationResult(ErrorMessage);
return ValidationResult.Success;
}
}
Обновление:
Если вам нужна проверка на стороне клиента для этого атрибута, вам необходимо реализовать интерфейс IClientModelValidator
:
public class DateLessThanAttribute : ValidationAttribute, IClientModelValidator
{
...
public void AddValidation(ClientModelValidationContext context)
{
var error = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-error", error);
}
}
Метод AddValidation
добавит атрибуты к вашим входам с context.Attributes
.
![введите описание изображения здесь]()
Вы можете прочитать здесь IClientModelValidator
Ответ 2
В качестве одного из возможных вариантов самооценки:
Вам просто нужно реализовать интерфейс IValidatableObject
с помощью метода Validate
, где вы можете поместить свой код проверки.
public class MyViewModel : IValidatableObject
{
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; } = DateTime.Parse("3000-01-01");
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
int result = DateTime.Compare(StartDate , EndDate);
if (result < 0)
{
yield return new ValidationResult("start date must be less than the end date!", new [] { "ConfirmEmail" });
}
}
}
Ответ 3
Основываясь на ответе Александра Гора, я предлагаю лучшую и общую проверку (и она совместима с ядром .Net). Если вы хотите сравнить свойства с помощью логики GreatherThan или LessThan (какими бы ни были типы), вы можете проверить, реализовали ли они интерфейс IComparable
. Если оба свойства допустимы, вы можете использовать реализацию CompareTo
. Это правило применяется также к типам DateTime
и DateTime
Меньше, чем
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class LessThanAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public LessThanAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
if (value.GetType() == typeof(IComparable))
{
throw new ArgumentException("value has not implemented IComparable interface");
}
var currentValue = (IComparable)value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
{
throw new ArgumentException("Comparison property with this name not found");
}
var comparisonValue = property.GetValue(validationContext.ObjectInstance);
if (comparisonValue.GetType() == typeof(IComparable))
{
throw new ArgumentException("Comparison property has not implemented IComparable interface");
}
if (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
{
throw new ArgumentException("The properties types must be the same");
}
if (currentValue.CompareTo((IComparable)comparisonValue) >= 0)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
Лучше чем
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class GreaterThanAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
public GreaterThanAttribute(string comparisonProperty)
{
_comparisonProperty = comparisonProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
if (value.GetType() == typeof(IComparable))
{
throw new ArgumentException("value has not implemented IComparable interface");
}
var currentValue = (IComparable)value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
{
throw new ArgumentException("Comparison property with this name not found");
}
var comparisonValue = property.GetValue(validationContext.ObjectInstance);
if (comparisonValue.GetType() == typeof(IComparable))
{
throw new ArgumentException("Comparison property has not implemented IComparable interface");
}
if (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
{
throw new ArgumentException("The properties types must be the same");
}
if (currentValue.CompareTo((IComparable)comparisonValue) < 0)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
В контексте бронирования пример может быть следующим:
public DateTime CheckInDate { get; set; }
[GreaterThan("CheckInDate", ErrorMessage = "CheckOutDate must be greater than CheckInDate")]
public DateTime CheckOutDate { get; set; }
Ответ 4
Вы можете сравнить две даты в методе IsValid.
public class CompareDates : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
//get your startdate & end date from model and value
//perform comparison
if (StartDate < EndDate)
{
return new ValidationResult
("start date must be less than the end date");
}
else
{
return ValidationResult.Success;
}
}
}
Ответ 5
На основе ответа Хайме и комментария Джеффри относительно необходимости наличия одного атрибута для Меньше, Меньше или равно, Равно, Больше, Больше или равно.
Приведенный ниже код будет обрабатывать все условия с помощью одного атрибута.
public enum ComparisonType
{
LessThan,
LessThanOrEqualTo,
EqualTo,
GreaterThan,
GreaterThanOrEqualTo
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class ComparisonAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
private readonly ComparisonType _comparisonType;
public ComparisonAttribute(string comparisonProperty, ComparisonType comparisonType)
{
_comparisonProperty = comparisonProperty;
_comparisonType = comparisonType;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
if (value.GetType() == typeof(IComparable))
{
throw new ArgumentException("value has not implemented IComparable interface");
}
var currentValue = (IComparable) value;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
{
throw new ArgumentException("Comparison property with this name not found");
}
var comparisonValue = property.GetValue(validationContext.ObjectInstance);
if (comparisonValue.GetType() == typeof(IComparable))
{
throw new ArgumentException("Comparison property has not implemented IComparable interface");
}
if (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
{
throw new ArgumentException("The properties types must be the same");
}
bool compareToResult;
switch (_comparisonType)
{
case ComparisonType.LessThan:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) >= 0;
break;
case ComparisonType.LessThanOrEqualTo:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) > 0;
break;
case ComparisonType.EqualTo:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) != 0;
break;
case ComparisonType.GreaterThan:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) <= 0;
break;
case ComparisonType.GreaterThanOrEqualTo:
compareToResult = currentValue.CompareTo((IComparable) comparisonValue) < 0;
break;
default:
throw new ArgumentOutOfRangeException();
}
return compareToResult ? new ValidationResult(ErrorMessage) : ValidationResult.Success;
}
}
В контексте бронирования пример может быть следующим:
public DateTime CheckInDate { get; set; }
[Comparison("CheckInDate", ComparisonType.EqualTo, ErrorMessage = "CheckOutDate must be equal to CheckInDate")]
public DateTime CheckOutDate { get; set; }
Ответ 6
Вот мой взгляд на это. Моя версия игнорирует свойства, которые являются нулевыми (необязательно). Очень хорошо подходит для веб-API.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class ComparisonAttribute : ValidationAttribute
{
private readonly string _comparisonProperty;
private readonly ComparisonType _comparisonType;
public ComparisonAttribute(string comparisonProperty, ComparisonType comparisonType)
{
_comparisonProperty = comparisonProperty;
_comparisonType = comparisonType;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
ErrorMessage = ErrorMessageString;
var property = validationContext.ObjectType.GetProperty(_comparisonProperty);
if (property == null)
throw new ArgumentException($"Property {_comparisonProperty} not found");
var right = property.GetValue(validationContext.ObjectInstance);
if (value is null || right is null)
return ValidationResult.Success;
if (value.GetType() == typeof(IComparable))
throw new ArgumentException($"The property {validationContext.MemberName} does not implement {typeof(IComparable).Name} interface");
if (right.GetType() == typeof(IComparable))
throw new ArgumentException($"The property {_comparisonProperty} does not implement {typeof(IComparable).Name} interface");
if (!ReferenceEquals(value.GetType(), right.GetType()))
throw new ArgumentException("The property types must be the same");
var left = (IComparable)value;
bool isValid;
switch (_comparisonType)
{
case ComparisonType.LessThan:
isValid = left.CompareTo((IComparable)right) < 0;
break;
case ComparisonType.LessThanOrEqualTo:
isValid = left.CompareTo((IComparable)right) <= 0;
break;
case ComparisonType.EqualTo:
isValid = left.CompareTo((IComparable)right) != 0;
break;
case ComparisonType.GreaterThan:
isValid = left.CompareTo((IComparable)right) > 0;
break;
case ComparisonType.GreaterThanOrEqualTo:
isValid = left.CompareTo((IComparable)right) >= 0;
break;
default:
throw new ArgumentOutOfRangeException();
}
return isValid
? ValidationResult.Success
: new ValidationResult(ErrorMessage);
}
public enum ComparisonType
{
LessThan,
LessThanOrEqualTo,
EqualTo,
GreaterThan,
GreaterThanOrEqualTo
}
}