mvc4 data annotation compare two dates

60,871

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

}


Share:
60,871
user2208349
Author by

user2208349

Updated on August 07, 2020

Comments

  • user2208349
    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 than startDate. I tried using [Compare("startDate")] but this only works for the equal operation.

    What should I use for the "greater than" operation?

  • Davor
    Davor over 9 years
    This wouldn't work on client side (JS), right? You'd get this message only on submit?
  • Igor Ralic
    Igor Ralic about 9 years
    As the return type is IEnumerable<ValidationResult>, doing "yield return new ValidationResult..." would yield a better result. :)
  • Shadi Alnamrouti
    Shadi Alnamrouti about 8 years
    This 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
    maracuja-juice over 7 years
    How do I display this message? My message doesn't get shown if I do: @Html.ValidationMessageFor(x => x.EndDate)
  • Davor Zlotrg
    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
    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
    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
    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
    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?