0

For my booking system, I need to check that the end date entered by the user in the form doesn't come before the start date entered by the user in the form. I am using ASP.NET MVC 4 C Sharp.

Can I do this using custom annotations? I have this so far but red line appears under the dates in the model class saying "An attribute argument must be a constant expression...."

    private readonly DateTime _startDate;
    private readonly DateTime _endDate;

    public DateComparisonAttribute(DateTime startDate, DateTime endDate) : base ("{1} is greater than {0}. The end date must come before start date")
    {
        _startDate = startDate;
        _endDate = endDate;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value != null)
        {
            if (_endDate < _startDate)
            {
                var errorMessage = FormatErrorMessage(validationContext.DisplayName);
                return new ValidationResult("End Date Must Come After Start Date");
            }
        }
        return ValidationResult.Success;
    }

Model:

    [DateComparison(StartDate, EndDate, ErrorMessage = "Dates")]
    [DisplayName("Start Date (MM/DD/YYYY)")]
    public DateTime StartDate { get; set; }
    [DisplayName("End Date (MM/DD/YYYY)")]
    public DateTime EndDate { get; set; }
John Saunders
  • 160,644
  • 26
  • 247
  • 397
ASPCoder1450
  • 1,651
  • 4
  • 23
  • 47
  • 1
    You need to write JavaScript code also. The C# is only one side of MVC validation – Liam Dec 12 '13 at 12:57
  • 1
    I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Dec 12 '13 at 12:58
  • @Liam: you should tell the OP why validation is required on both sides – John Saunders Dec 12 '13 at 12:59
  • I'm pretty sure that won't compile either, your StartDate, etc. can't be passed into an attribute – Liam Dec 12 '13 at 12:59
  • The problem is DateTime return a runtime object, while in attribute we need the parameter fixed at the compile-time – Nilesh Gajare Dec 12 '13 at 13:00

2 Answers2

1

Here's one I wrote previously built from information from Brad wilsons blog

C# Attribute

public sealed class IsDateAfterAttribute: ValidationAttribute, IClientValidatable
  {
    protected abstract string GetValidationType();
    protected abstract bool CompareValues(DateTime value, DateTime propertyTestedValue, out ValidationResult validationResult);

    protected readonly string testedPropertyName;
    protected readonly bool allowEqualDates;

    protected int _maxSearchableDaysAhead;

    public IsDateAfterAttribute(string testedPropertyName, bool allowEqualDates = false)
    {
      this.testedPropertyName = testedPropertyName;
      this.allowEqualDates = allowEqualDates;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
      var propertyTestedInfo = validationContext.ObjectType.GetProperty(this.testedPropertyName);
      if (propertyTestedInfo == null)
      {
        return new ValidationResult(string.Format("unknown property {0}", this.testedPropertyName));
      }

      var propertyTestedValue = propertyTestedInfo.GetValue(validationContext.ObjectInstance, null);

      if (value == null || !(value is DateTime))
      {
        return ValidationResult.Success;
      }

      if (propertyTestedValue == null || !(propertyTestedValue is DateTime))
      {
        return ValidationResult.Success;
      }

      ValidationResult returnVal;
      if (CompareValues((DateTime)value, (DateTime)propertyTestedValue, out returnVal))
      {
        return returnVal;
      }
      else
      {
        return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
      }



    }

    protected override bool CompareValues(DateTime value, DateTime propertyTestedValue, out ValidationResult validationResult)
{
  validationResult = null;
  // Compare values
  if (value <= propertyTestedValue)
  {
    if (this.allowEqualDates)
    {
      validationResult = ValidationResult.Success;
      return true;
    }
    if (value < propertyTestedValue)
    {
      validationResult = ValidationResult.Success;
      return true;
    }

  }


  return false;
}

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {

      this._maxSearchableDaysAhead = Setting.GetSettingValue(SettingNames.MaxSearchableDaysAhead, 548);

      var rule = new ModelClientValidationRule
      {
        ErrorMessage = string.Format(this.ErrorMessageString, _maxSearchableDaysAhead),
        ValidationType = "isdateafter"
      };
      rule.ValidationParameters["propertytested"] = this.testedPropertyName;
      rule.ValidationParameters["allowequaldates"] = this.allowEqualDates;
      yield return rule;
    }
  }

JS

$.validator.unobtrusive.adapters.add(
    'isdateafter', ['propertytested', 'allowequaldates'], function (options) {
        options.rules['isdateafter'] = options.params;
        options.messages['isdateafter'] = options.message;
    });
    $.validator.addMethod("isdateafter", function (value, element, params) {
        var parts = element.name.split(".");
        var prefix = "";
        if (parts.length > 1)
            prefix = parts[0] + ".";
        var startdatevalue = $('input[name="' + prefix + params.propertytested + '"]').val();
        if (!value || !startdatevalue)
            return true;
        if (params.allowequaldates && params.allowequaldates.toLowerCase() == "true") 
                {
                    return Date.parse(startdatevalue) <= Date.parse(value); 
                }
                else
                {
                    return Date.parse(startdatevalue) < Date.parse(value);
                }
    });

Added to Model

[DisplayName("Departure Dates")]
 public DateTime? DepartureFrom { get; set; }

 [DisplayName("Departure Dates")]
 [IsDateAfter("DepartureFrom", true, ErrorMessage = "* Departure To Date must be after Departure From Date")]
 public DateTime? DepartureTo { get; set; }
Liam
  • 27,717
  • 28
  • 128
  • 190
  • This solution uses expensive reflection. Not something I would want to add to many models. – Maess Dec 12 '13 at 13:12
  • the [Cost of Reflection is much exagerated](http://stackoverflow.com/a/771533/542251) – Liam Oct 20 '16 at 16:20
  • 1
    Since the answer was given the 'nameof' operator was introduced - adding it to the attribute declaration [IsDateAfter(nameof(DepartureFrom), makes it a little more robust when the attribute name is refactored. Also there is the tradeof between using attributes that can hookup with client side validation, and using IValidateableObject for server side validation without reflection: it turns out that a mixed modus never processes both attributes AND IValidateableObject.Validate in a single pass. Only when there are no attributes (or: none that fail validation) the interface is triggered. – Arno Peters Aug 24 '17 at 10:36
-1

Attributes must be constants as they are added to the assembly when it is compiled. So, no you can't get what you are after with a custom annotation.

Maess
  • 4,118
  • 20
  • 29
  • Your wrong, out the box no, but you can create your own custom one. – Liam Dec 12 '13 at 13:09
  • You are wrong, the values used in an attribute must be constants. Regardless of whether the attribute is stock or custom. – Maess Dec 12 '13 at 13:10
  • You can use reflection and Jquery. See my answer – Liam Dec 12 '13 at 13:12
  • Yes with huge performance implications. Validations of this type are best done server side. – Maess Dec 12 '13 at 13:13
  • Reflection does not add *huge* performance implications. Reflection add's an overhead, yes, it is not *huge*. It's so small that I defy you to notice any difference in the average system. [See here](http://stackoverflow.com/questions/25458/how-costly-is-net-reflection) – Liam Dec 12 '13 at 13:15
  • Also if the choice is, *you can't do it* or *use reflection*, reflection wins surely. Or do you simply not validate dates in this manner on your web site at all?! – Liam Dec 12 '13 at 13:18
  • No, you validate them server side, using a validation method. – Maess Dec 12 '13 at 13:21