mvc4 data annotation compare two dates
Solution 1
Take a look at Fluent Validation or MVC Foolproof Validation: those can help you a lot.
With Foolproof for example there is a [GreaterThan("StartDate")]
annotation than you can use on your date property.
Or if you don't want to use other libraries, you can implement your own custom validation by implementing IValidatableObject
on your model:
public class ViewModel: IValidatableObject
{
[Required]
public DateTime StartDate { get; set; }
[Required]
public DateTime EndDate { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (EndDate < StartDate)
{
yield return new ValidationResult(
errorMessage: "EndDate must be greater than StartDate",
memberNames: new[] { "EndDate" }
);
}
}
}
Solution 2
IValidatableObject interface provides a way for an object to be validated which implements IValidatableObject.Validate(ValidationContext validationContext) method. This method always return IEnumerable object. That's why you should create list of ValidationResult objects and errors are added to this and return. Empty list means to validate your conditions. That is as follows in mvc 4...
public class LibProject : IValidatableObject
{
[Required(ErrorMessage="Project name required")]
public string Project_name { get; set; }
[Required(ErrorMessage = "Job no required")]
public string Job_no { get; set; }
public string Client { get; set; }
[DataType(DataType.Date,ErrorMessage="Invalid Date")]
public DateTime ExpireDate { get; set; }
IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
{
List < ValidationResult > res =new List<ValidationResult>();
if (ExpireDate < DateTime.Today)
{
ValidationResult mss=new ValidationResult("Expire date must be greater than or equal to Today");
res.Add(mss);
}
return res;
}
}
Solution 3
Borrowing heavily from the responses from Alexander Gore and Jaime Marín in a related StackOverflow question1, I created five classes that enable comparing two fields in the same model using GT, GE, EQ, LE, and LT operators, provided they implement IComparable. So it can be used for pairs of dates, times, integers, and strings, for example.
It would be nice to merge these all into one class and take the operator as an argument, but I don't know how. I left the three exceptions as is because, if thrown, they really represent a form design problem, not a user input problem.
You just use it in your model like this, and the file with the five classes follows:
[Required(ErrorMessage = "Start date is required.")]
public DateTime CalendarStartDate { get; set; }
[Required(ErrorMessage = "End date is required.")]
[AttributeGreaterThanOrEqual("CalendarStartDate",
ErrorMessage = "The Calendar end date must be on or after the Calendar start date.")]
public DateTime CalendarEndDate { get; set; }
using System;
using System.ComponentModel.DataAnnotations;
//Contains GT, GE, EQ, LE, and LT validations for types that implement IComparable interface.
//https://stackoverflow.com/questions/41900485/custom-validation-attributes-comparing-two-properties-in-the-same-model
namespace DateComparisons.Validations
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class AttributeGreaterThan : ValidationAttribute
{
private readonly string _comparisonProperty;
public AttributeGreaterThan(string comparisonProperty){_comparisonProperty = comparisonProperty;}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if(value==null) return new ValidationResult("Invalid entry");
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(!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
throw new ArgumentException("The types of the fields to compare are not the same.");
return currentValue.CompareTo((IComparable)comparisonValue) > 0 ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class AttributeGreaterThanOrEqual : ValidationAttribute
{
private readonly string _comparisonProperty;
public AttributeGreaterThanOrEqual(string comparisonProperty) { _comparisonProperty = comparisonProperty; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return new ValidationResult("Invalid entry");
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 (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
throw new ArgumentException("The types of the fields to compare are not the same.");
return currentValue.CompareTo((IComparable)comparisonValue) >= 0 ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class AttributeEqual : ValidationAttribute
{
private readonly string _comparisonProperty;
public AttributeEqual(string comparisonProperty) { _comparisonProperty = comparisonProperty; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return new ValidationResult("Invalid entry");
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 (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
throw new ArgumentException("The types of the fields to compare are not the same.");
return currentValue.CompareTo((IComparable)comparisonValue) == 0 ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class AttributeLessThanOrEqual : ValidationAttribute
{
private readonly string _comparisonProperty;
public AttributeLessThanOrEqual(string comparisonProperty) { _comparisonProperty = comparisonProperty; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return new ValidationResult("Invalid entry");
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 (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
throw new ArgumentException("The types of the fields to compare are not the same.");
return currentValue.CompareTo((IComparable)comparisonValue) <= 0 ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter)]
public class AttributeLessThan : ValidationAttribute
{
private readonly string _comparisonProperty;
public AttributeLessThan(string comparisonProperty) { _comparisonProperty = comparisonProperty; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value == null) return new ValidationResult("Invalid entry");
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 (!ReferenceEquals(value.GetType(), comparisonValue.GetType()))
throw new ArgumentException("The types of the fields to compare are not the same.");
return currentValue.CompareTo((IComparable)comparisonValue) < 0 ? ValidationResult.Success : new ValidationResult(ErrorMessage);
}
}
}
user2208349
Updated on August 07, 2020Comments
-
user2208349 over 3 years
I have these two fields in my model:
[Required(ErrorMessage="The start date is required")] [Display(Name="Start Date")] [DisplayFormat(DataFormatString = "{0,d}")] public DateTime startDate { get; set; } [Required(ErrorMessage="The end date is required")] [Display(Name="End Date")] [DisplayFormat(DataFormatString = "{0,d}")] public DateTime endDate{ get; set; }
I require that
endDate
must be greater thanstartDate
. I tried using[Compare("startDate")]
but this only works for the equal operation.What should I use for the "greater than" operation?
-
Davor over 9 yearsThis wouldn't work on client side (JS), right? You'd get this message only on submit?
-
Igor Ralic about 9 yearsAs the return type is IEnumerable<ValidationResult>, doing "yield return new ValidationResult..." would yield a better result. :)
-
Shadi Alnamrouti about 8 yearsThis solution is better than the chosen answer because you will need list of validation results in case you have more than one case to compare. Thanks.
-
maracuja-juice over 7 yearsHow do I display this message? My message doesn't get shown if I do:
@Html.ValidationMessageFor(x => x.EndDate)
-
Davor Zlotrg over 7 years@Marimba check the edit. To include a validation message to a specific field there is a construstor overload that accepts a list of member names.
-
Anil P Babu over 7 years@DZL I didnt get you on your reply to Marimba. can you tell us with an example?
-
Davor Zlotrg over 7 years@anil_pulikoden it is this part of the code
new ValidationResult(errorMessage: "EndDate must be greater than StartDate", memberNames: new[] { "EndDate" });
-
Anil P Babu over 7 years@DZL Are you sure it will show validation message using
@Html.ValidationMessageFor(x => x.EndDate)
before submit? It didn't show up for me. -
Muhammad Ashikuzzaman over 6 years@DZL nice answer, but how will now see the error message in controller action so that I can pass the error message to client site?