Пользовательские атрибуты проверки: сравнение двух свойств в одной и той же модели

Есть ли способ создать настраиваемый атрибут в ядре 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
    }
}