13

I have a ViewModel for my MVC4 Prject containing two DateTime properties:

[Required]
[DataType(DataType.Date)]
public DateTime RentDate { get; set; }

[Required]
[DataType(DataType.Date)]
public DateTime ReturnDate { get; set; }

Is there a simple way to use C# attributes as [Compare("someProperty")] to check weather the value of RentDate property is earlier than the value of ReturnDate?

cLar
  • 280
  • 1
  • 3
  • 17
  • 1
    Why would you want to use *attributes* for that? When would you want it to be applied? As validation? – Jon Skeet Dec 07 '12 at 14:09
  • I believe that should be validated on clientside via validation lib. Then, to be safe, you could validate on back-end, adding errors to modelstate. – DeeDub Dec 07 '12 at 14:25
  • Why do you want to compare? Is your purpose to limit the value of one to the other? You could try searching for 'coercion', that's how it is called in WPF. Unfortunately can't help you with asp.net. – Steven Jeuris Dec 07 '12 at 14:37
  • In order to simplify applying coercion to properties in WPF [I actually used attributes as well](https://github.com/Whathecode/Framework-Class-Library-Extension/blob/master/Whathecode.PresentationFramework.Aspects.Tests/Windows/DependencyPropertyFactory/Attributes/CoercionHandlersTest.cs), since I considered it to be ['meta-behavior'](http://whathecode.wordpress.com/2011/09/22/attribute-metabehavior/) of those properties. – Steven Jeuris Dec 07 '12 at 14:41
  • Yes, its used as server-side validation. I need this as part of a project for a University course. – cLar Dec 08 '12 at 10:01

6 Answers6

27

Here is a very quick basic implementation (without error checking etc.) that should do what you ask (only on server side...it will not do asp.net client side javascript validation). I haven't tested it, but should be enough to get you started.

using System;
using System.ComponentModel.DataAnnotations;

namespace Test
{
   [AttributeUsage(AttributeTargets.Property)]
   public class DateGreaterThanAttribute : ValidationAttribute
   {
      public DateGreaterThanAttribute(string dateToCompareToFieldName)
      {
          DateToCompareToFieldName = dateToCompareToFieldName;
      }

       private string DateToCompareToFieldName { get; set; }

       protected override ValidationResult IsValid(object value, ValidationContext validationContext)
       {
           DateTime earlierDate = (DateTime)value;

           DateTime laterDate = (DateTime)validationContext.ObjectType.GetProperty(DateToCompareToFieldName).GetValue(validationContext.ObjectInstance, null);

           if (laterDate > earlierDate)
           {
               return ValidationResult.Success;
           }
           else
           {
               return new ValidationResult("Date is not later");
           }
       }
   }


   public class TestClass
   {
       [DateGreaterThan("ReturnDate")]
       public DateTime RentDate { get; set; }

       public DateTime ReturnDate { get; set; }
   }
}
IAmGroot
  • 13,760
  • 18
  • 84
  • 154
Mike Hanrahan
  • 1,192
  • 8
  • 18
  • Isn't the property to which I assign this attribute to (which is the `value` object), the LATER date? So from a readability perspective I would switch those. `RentDate` gets the attribute and `RentDate` has `DateGreaterThan` `ReturnDate`. – timmkrause Nov 29 '19 at 14:47
6

It looks like you're using DataAnnotations so another alternative is to implement IValidatableObject in the view model:

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    if (this.RentDate > this.ReturnDate)
    {
        yield return new ValidationResult("Rent date must be prior to return date", new[] { "RentDate" });
    }
}
Trevor Pilley
  • 16,156
  • 5
  • 44
  • 60
2

If you're using .Net Framework 3.0 or higher you could do it as a class extension...

    /// <summary>
    /// Determines if a <code>DateTime</code> falls before another <code>DateTime</code> (inclusive)
    /// </summary>
    /// <param name="dt">The <code>DateTime</code> being tested</param>
    /// <param name="compare">The <code>DateTime</code> used for the comparison</param>
    /// <returns><code>bool</code></returns>
    public static bool isBefore(this DateTime dt, DateTime compare)
    {
        return dt.Ticks <= compare.Ticks;
    }

    /// <summary>
    /// Determines if a <code>DateTime</code> falls after another <code>DateTime</code> (inclusive)
    /// </summary>
    /// <param name="dt">The <code>DateTime</code> being tested</param>
    /// <param name="compare">The <code>DateTime</code> used for the comparison</param>
    /// <returns><code>bool</code></returns>
    public static bool isAfter(this DateTime dt, DateTime compare)
    {
        return dt.Ticks >= compare.Ticks;
    }
Kevin
  • 704
  • 3
  • 4
  • (1) Question is about attributes not extensions, (2) DateTime is a struct so natively compares using binary operators, no need to compare by ticks or use extension methods, (3) C# method naming convention is PascalCase not camelCase, (4) A reasonable way to do something in Java is not necessarily a reasonable way to do it in C#. – gknicker Apr 28 '18 at 13:19
1

Not sure of it's use in MVC/Razor, but..

You can use DateTime.Compare(t1, t2) - t1 and t2 being the times you want to compare. It will return either -1, 0 or 1 depending on what the results are.

Read more here: http://msdn.microsoft.com/en-us/library/system.datetime.compare.aspx

iajs
  • 167
  • 4
  • 14
1

Model:

[DateCorrectRange(ValidateStartDate = true, ErrorMessage = "Start date shouldn't be older than the current date")]
public DateTime StartDate { get; set; }

 [DateCorrectRange(ValidateEndDate = true, ErrorMessage = "End date can't be younger than start date")]
public DateTime EndDate { get; set; }

Attribute class:

[AttributeUsage(AttributeTargets.Property)]
    public class DateCorrectRangeAttribute : ValidationAttribute
    {
        public bool ValidateStartDate { get; set; }
        public bool ValidateEndDate { get; set; }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var model = validationContext.ObjectInstance as YourModelType;

            if (model != null)
            {
                if (model.StartDate > model.EndDate && ValidateEndDate
                    || model.StartDate > DateTime.Now.Date && ValidateStartDate)
                {
                    return new ValidationResult(string.Empty);
                }
            }

            return ValidationResult.Success;
        }
    }
0

Nothing like that exists in the framework. A common similar attribute is the RequiredIf. It is described in this post.

RequiredIf Conditional Validation Attribute

Community
  • 1
  • 1
Daniel A. White
  • 187,200
  • 47
  • 362
  • 445