Compare Dates DataAnnotations Validation asp.net mvc

前端 未结 5 1525
半阙折子戏
半阙折子戏 2020-12-15 13:11

Lets say I have a StartDate and an EndDate and I wnt to check if the EndDate is not more then 3 months apart from the Start Date

public class DateCompare : V         


        
相关标签:
5条回答
  • 2020-12-15 13:35

    The Attribute

    public class CompareValidatorAttribute : ValidationAttribute, IInstanceValidationAttribute
    {
        public CompareValidatorAttribute(string prefix, string propertyName) {
            Check.CheckNullArgument("propertyName", propertyName);
    
            this.propertyName = propertyName;
            this.prefix = prefix;
        }
    
        string propertyName, prefix;
    
        public string PropertyName
        {
            get { return propertyName; }
        }
    
        public string Prefix
        {
            get { return prefix; }
        }
    
        #region IInstanceValidationAttribute Members
    
        public bool IsValid(object instance, object value)
        {
            var property = instance.GetType().GetProperty(propertyName);
    
            var targetValue = property.GetValue(instance, null);
            if ((targetValue == null && value == null) || (targetValue != null && targetValue.Equals(value)))
                return true;
    
            return false;
        }
    
        #endregion
    
        public override bool IsValid(object value)
        {
            throw new NotImplementedException();
        }
    }
    

    The Interface

    public interface IInstanceValidationAttribute
    {
        bool IsValid(object instance, object value);
    }
    

    The Validator

    public class CompareValidator : DataAnnotationsModelValidator<CompareValidatorAttribute>
    {
        public CompareValidator(ModelMetadata metadata, ControllerContext context, CompareValidatorAttribute attribute)
            : base(metadata, context, attribute)
        {
        }
    
        public override IEnumerable<ModelValidationResult> Validate(object container)
        {
            if (!(Attribute as IInstanceValidationAttribute).IsValid(container, Metadata.Model))
                yield return (new ModelValidationResult
                {
                    MemberName = Metadata.PropertyName,
                    Message = Attribute.ErrorMessage
                });
        }
    
        public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
        {
            var rule = new ModelClientValidationRule() { ErrorMessage = Attribute.ErrorMessage, ValidationType = "equalTo" };
            rule.ValidationParameters.Add("equalTo", "#" + (!string.IsNullOrEmpty(Attribute.Prefix) ? Attribute.Prefix + "_" : string.Empty)+ Attribute.PropertyName);
    
            return new[] { rule };
        }
    }
    

    Register it:

    DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CompareValidatorAttribute), typeof(CompareValidator));
    
    0 讨论(0)
  • 2020-12-15 13:43

    It's a late answer but I wanted to share it for others outhere. Here's how I've done it so that everything is validated using unobtrusive client validation:

    1. Create an attribute class:

      public class DateCompareValidationAttribute : ValidationAttribute, IClientValidatable
      {
      
        public enum CompareType
        {
            GreatherThen,
            GreatherThenOrEqualTo,
            EqualTo,
            LessThenOrEqualTo,
            LessThen
        }
      
      
      
      
        private CompareType _compareType;
        private DateTime _fromDate;
        private DateTime _toDate;
      
        private string _propertyNameToCompare;
      
        public DateCompareValidationAttribute(CompareType compareType, string message, string compareWith = "")
      {
          _compareType = compareType;
          _propertyNameToCompare = compareWith;
          ErrorMessage = message;
      }
      
      
      #region IClientValidatable Members
      /// <summary>
      /// Generates client validation rules
      /// </summary>
      public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
      {
          ValidateAndGetCompareToProperty(metadata.ContainerType);
          var rule = new ModelClientValidationRule();
      
          rule.ErrorMessage = ErrorMessage;
          rule.ValidationParameters.Add("comparetodate", _propertyNameToCompare);
          rule.ValidationParameters.Add("comparetype", _compareType);
          rule.ValidationType = "compare";
      
          yield return rule;
      }
      
      #endregion
      
      
       protected override ValidationResult IsValid(object value, ValidationContext validationContext)
      {
           // Have to override IsValid method. If you have any logic for server site validation, put it here. 
          return ValidationResult.Success;
      
      }
      
      /// <summary>
      /// verifies that the compare-to property exists and of the right types and returnes this property
      /// </summary>
      /// <param name="containerType">Type of the container object</param>
      /// <returns></returns>
      private PropertyInfo ValidateAndGetCompareToProperty(Type containerType)
      {
          var compareToProperty = containerType.GetProperty(_propertyNameToCompare);
          if (compareToProperty == null)
          {
              string msg = string.Format("Invalid design time usage of {0}. Property {1} is not found in the {2}", this.GetType().FullName, _propertyNameToCompare, containerType.FullName);
              throw new ArgumentException(msg);
          }
          if (compareToProperty.PropertyType != typeof(DateTime) && compareToProperty.PropertyType != typeof(DateTime?))
          {
              string msg = string.Format("Invalid design time usage of {0}. The type of property {1} of the {2} is not DateType", this.GetType().FullName, _propertyNameToCompare, containerType.FullName);
              throw new ArgumentException(msg);
          }
      
          return compareToProperty;
      }
      }
      

      Note: if you want to validate length of time, add another parameter to the constractor and change enumerator for this specific type of comparsion

    2. Add the attributes to the field as folows:
      [DateCompareValidation(DateCompareValidationAttribute.CompareType.GreatherThenOrEqualTo, "This Date must be on or after another date", compareWith: "AnotherDate")]

    3. Take a note how your generated html is changed. It should include your validation message, field name for the compare-to date, etc. The generated parms would start with "data-val-compare". You defined this "compare" when you set ValidationType="compare" in GetClientValidationRules method.

    4. Now you need matching javascript code: to add an validation adapter and validation method. I used anonimous method here, but you don't have to. I recommend placing this code in a separate javascript file so that this file together with your attribute class become like a control and could be used anywhere.

    $.validator.unobtrusive.adapters.add( 'compare', ['comparetodate', 'comparetype'], function (options) { options.rules['compare'] = options.params; options.messages['compare'] = options.message; } );

    $.validator.addMethod("compare", function (value, element, parameters) {
        // value is the actuall value entered 
        // element is the field itself, that contain the the value (in case the value is not enough)
    
        var errMsg = "";
        // validate parameters to make sure everyting the usage is right
        if (parameters.comparetodate == undefined) {
            errMsg = "Compare validation cannot be executed: comparetodate parameter not found";
            alert(errMsg);
            return false;
        }
        if (parameters.comparetype == undefined) {
            errMsg = "Compare validation cannot be executed: comparetype parameter not found";
            alert(errMsg);
            return false;
        }
    
    
        var compareToDateElement = $('#' + parameters.comparetodate).get();
        if (compareToDateElement.length == 0) {
            errMsg = "Compare validation cannot be executed: Element to compare " + parameters.comparetodate + " not found";
            alert(errMsg);
            return false;
        }
        if (compareToDateElement.length > 1) {
            errMsg = "Compare validation cannot be executed: more then one Element to compare with id " + parameters.comparetodate + " found";
            alert(errMsg);
            return false;
        }
        //debugger;
    
        if (value && !isNaN(Date.parse(value))) {
            //validate only the value contains a valid date. For invalid dates and blanks non-custom validation should be used    
            //get date to compare
            var compareToDateValue = $('#' + parameters.comparetodate).val();
            if (compareToDateValue && !isNaN(Date.parse(compareToDateValue))) {
                //if date to compare is not a valid date, don't validate this
                switch (parameters.comparetype) {
                    case 'GreatherThen':
                        return new Date(value) > new Date(compareToDateValue);
                    case 'GreatherThenOrEqualTo':
                        return new Date(value) >= new Date(compareToDateValue);
                    case 'EqualTo':
                        return new Date(value) == new Date(compareToDateValue);
                    case 'LessThenOrEqualTo':
                        return new Date(value) <= new Date(compareToDateValue);
                    case 'LessThen':
                        return new Date(value) < new Date(compareToDateValue);
                    default:
                        {
                            errMsg = "Compare validation cannot be executed: '" + parameters.comparetype + "' is invalid for comparetype parameter";
                            alert(errMsg);
                            return false;
                        }
                }
                return true;
            }
            else
                return true;
    
        }
        else
            return true;
    });
    

    This takes care only of client-side unobtrusive validation. If you need server side, you'd have to have some logic in the override of isValid method. Also, you can use Reflection to generate error message using display attributes, etc and make the message argument optional.

    0 讨论(0)
  • 2020-12-15 13:46
    1. Dates

    Entity:

    [MetadataType(typeof(MyEntity_Validation))]
    public partial class MyEntity
    {
    }
    public class MyEntity_Validation
    {
        [Required(ErrorMessage="'Date from' is required")]
        public DateTime DateFrom { get; set; }
    
        [CompareDatesValidatorAttribute("DateFrom")]  
        public DateTime DateTo { get; set; }
    }
    

    Attribute:

     public sealed class CompareDatesValidatorAttribute : ValidationAttribute
    {
        private string _dateToCompare;  
        private const string _errorMessage = "'{0}' must be greater or equal'{1}'";  
    
        public CompareDatesValidatorAttribute(string dateToCompare)
            : base(_errorMessage)
        {
            _dateToCompare = dateToCompare;
        }
    
        public override string FormatErrorMessage(string name)
        {
            return string.Format(_errorMessage, name, _dateToCompare);
        } 
    
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var dateToCompare = validationContext.ObjectType.GetProperty(_dateToCompare);
            var dateToCompareValue = dateToCompare.GetValue(validationContext.ObjectInstance, null);
            if (dateToCompareValue != null && value != null && (DateTime)value < (DateTime)dateToCompareValue) 
            {
                return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
            }
            return null;
        }
    }
    

    2. Password

    Entity:

       public string Password { get; set; }
    
        [Compare("Password", ErrorMessage = "ConfirmPassword must match Password")]
        public string ConfirmPassword { get; set; }
    

    I hope it helps

    0 讨论(0)
  • 2020-12-15 13:55

    I have only figured out how to do this at the class level but not the at property level. If you create an MVC application, the Account model shows the approach illustrated below.

    Class:

      [PropertiesMustMatch("Password",
                "ConfirmPassword", ErrorMessage =
                "Password and confirmation password
                do not match.")]
                    public class RegisterModel
                    {
    
                        [Required(ErrorMessage = "Required")]
                        [DataType(DataType.EmailAddress)]
                        [DisplayName("Your Email")]
                        public string Email { get; set; }              
    
                        [Required(ErrorMessage = "Required")]
                        [ValidatePasswordLength]
                        [DataType(DataType.Password)]
                        [DisplayName("Password")]
                        public string Password { get; set; }
    
                        [Required(ErrorMessage = "Required")]
                        [DataType(DataType.Password)]
                        [DisplayName("Re-enter password")]
                        public string ConfirmPassword { get; set; }                
                    }
    

    Validation Method:

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
        public sealed class PropertiesMustMatchAttribute : ValidationAttribute
        {
            private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";
    
            private readonly object _typeId = new object();
    
            public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
                : base(_defaultErrorMessage)
            {
                OriginalProperty = originalProperty;
                ConfirmProperty = confirmProperty;
            }
    
            public string ConfirmProperty
            {
                get;
                private set;
            }
    
            public string OriginalProperty
            {
                get;
                private set;
            }
    
            public override object TypeId
            {
                get
                {
                    return _typeId;
                }
            }
    
            public override string FormatErrorMessage(string name)
            {
                return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
                    OriginalProperty, ConfirmProperty);
            }
    
            public override bool IsValid(object value)
            {
                PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
                object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
                object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
                return Object.Equals(originalValue, confirmValue);
            }
    }
    
    0 讨论(0)
  • 2020-12-15 14:01

    Thanks for the info. I was left scratching my head when I wanted to bind the validation message to a property. If you switch the line

    [AttributeUsage(AttributeTargets.Class)]
    

    to...

    [AttributeUsage(AttributeTargets.Property)]
    

    you can move the compare above a specific property. Thanks for the info! Much help since my client is still running on 3.5 sp1. sad face

    0 讨论(0)
提交回复
热议问题