Validation custom attributes: comparing two properties in the same model

Is there a way to create a custom attribute in the ASP.NET kernel to check if one date property is less than another date property in the model using ValidationAttribute .

Suppose I have this:

 public class MyViewModel { [Required] [CompareDates] public DateTime StartDate { get; set; } [Required] public DateTime EndDate { get; set; } = DateTime.Parse("3000-01-01"); } 

I am trying to use something like this:

  public class CompareDates : ValidationAttribute { public CompareDates() : base("") { } public override bool IsValid(object value) { return base.IsValid(value); } } 

I found another SO post that suggests using a different library, but I prefer sticking with ValidationAttribute if feasible.

+16
source share
6 answers

You can create your own validation attribute to compare two properties. This is a server side check:

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

Update : If you need client-side validation for this attribute, you need to implement the IClientModelValidator interface:

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

The AddValidation method will add attributes to your inputs with context.Attributes .

enter image description here

You can read here IClientModelValidator

+33
source

As one of the possible options for self-esteem :

You just need to implement the IValidatableObject interface using the Validate method, 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" }); } } } 
+13
source

Based on Alexander Gore's answer, I offer a better and general test (and it is compatible with the .Net core). If you want to compare properties using GreatherThan or LessThan logic (whatever the types are), you can check to see if they implemented the IComparable interface. If both properties are valid, you can use the CompareTo implementation. This rule also applies to the DateTime and DateTime types.

Less than

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

Better than

 [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 the context of a reservation, an example could be:

 public DateTime CheckInDate { get; set; } [GreaterThan("CheckInDate", ErrorMessage = "CheckOutDate must be greater than CheckInDate")] public DateTime CheckOutDate { get; set; } 
+3
source

You can compare two dates in the IsValid method.

 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; } } } 
+2
source

Based on Jaime’s answer and Jeffrey’s comment regarding the need for one attribute for Less, Less, or Equal, Equal, Greater, Greater, or Equal.

The code below 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 context of a reservation, an example could be:

 public DateTime CheckInDate { get; set; } [Comparison("CheckInDate", ComparisonType.EqualTo, ErrorMessage = "CheckOutDate must be equal to CheckInDate")] public DateTime CheckOutDate { get; set; } 
+1
source

Here is my view on this. My version ignores properties that are null (optional). Very suitable for web 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 } } 
0
source

Source: https://habr.com/ru/post/1263463/


All Articles