Custom Validation Attributes: Comparing two properties in the same model

前端 未结 7 398
谎友^
谎友^ 2020-12-01 06:39

Is there a way to create a custom attribute in ASP.NET Core to validate if one date property is less than other date property in a model using ValidationAttribute<

相关标签:
7条回答
  • 2020-12-01 06:59

    Based on Jaime answer and Jeffrey's comment regarding needing a single attribute for Less Than, Less Than or Equal to, Equal To, Greater Than, Greater Than or Equal to.

    The below code will handle all conditions with a single attribute.

    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;
        }
    }
    

    In the booking context, an example would be as follows:

    public DateTime CheckInDate { get; set; }
    
    [Comparison("CheckInDate", ComparisonType.EqualTo, ErrorMessage = "CheckOutDate must be equal to CheckInDate")]
    public DateTime CheckOutDate { get; set; }
    
    0 讨论(0)
  • 2020-12-01 07:01

    You could compare the two dates in the IsValid() method.

    public class CompareDates : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            // get your StartDate and EndDate 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;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 07:03

    Based on the Alexander Gore response, I suggest a better and generic validation (and it is .Net core compatible). When you want to compare properties using GreatherThan or LessThan logic (whatever the types are), you could validate if they have implemented the IComparable interface. If both properties are valid you could use the CompareTo implementation. This rule applies for DateTime and number types as well

    LessThan

    [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;
        }
    }
    

    GreaterThan

    [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;
        }
    }
    

    In a booking context an example could be as follow:

    public DateTime CheckInDate { get; set; }
    
    [GreaterThan("CheckInDate", ErrorMessage = "CheckOutDate must be greater than CheckInDate")]
    public DateTime CheckOutDate { get; set; }
    
    0 讨论(0)
  • 2020-12-01 07:03

    I created a library with the most common custom validations in ASP.NET Core. The library also has the client-side validation for all the server-side custom validations. The library solves OP's problem with a single attribute as follows:

    // If you want the StartDate to be smaller than the EndDate:
    [CompareTo(nameof(EndDate), ComparisionType.SmallerThan)] 
    public DateTime StartDate { get; set; }
    

    Here is the GitHub link of the library: AspNetCore.CustomValidation

    Currently, the library contains the following validation attributes:

    1. FileAttribute - To validate file type, file max size, file min size;

    2. MaxAgeAttribute - To validate maximum age against date of birth value of DateTime type;

    3. MinAgeAttribute - To validate minimum required age against a date of birth value of DateTime type;

    4. MaxDateAttribute -To set max value validation for a DateTime field;

    5. MinDateAttribute - To set min value validation for a DateTime field;

    6. CompareToAttibute – To compare one property value against another property value;

    7. TinyMceRequiredAttribute -To enforce required validation attribute on the online text editors like TinyMCE, CkEditor, etc.

    0 讨论(0)
  • 2020-12-01 07:08

    As one possible option self-validation:

    You just need to Implement an interface IValidatableObject with the method Validate(), where you can put your validation code.

    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" });
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-01 07:17

    Here is my take on this. My version ignores properties which are null (optional). It fits very well when applied to web APIs.

    [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
        }
    }
    
    0 讨论(0)
提交回复
热议问题