title: ASP.Net MVC Conditional Validation: End date must be greater than or equal to start date
body: <p>i could do it server side part. i want if start date is not empty then end date must be equal or greater than start date. server side logic is almost complete but i am bit confuse what to do for client side logic. basically i want to do it by <code>IClientValidatable</code> interface.</p>
  
  <p>my server side code as follows</p>
  
  <h2>Model code</h2>
  
  <pre><code>   public class DateValTest
      {
          [Display(Name = "Start Date")]
          [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
          public DateTime? StartDate { get; set; }
  
          [Display(Name = "End Date")]
          [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
          [DateGreaterThanAttribute(otherPropertyName = "StartDate", ErrorMessage = "End date must be greater than start date")]
          public DateTime?  EndDate { get; set; }
      }
  </code></pre>
  
  <h2>attribute code</h2>
  
  <pre><code> public class DateGreaterThanAttribute : ValidationAttribute
      {
          public string otherPropertyName;
          public DateGreaterThanAttribute() { }
          public DateGreaterThanAttribute(string otherPropertyName, string errorMessage)
              : base(errorMessage)
          {
              this.otherPropertyName = otherPropertyName;
          }
  
          protected override ValidationResult IsValid(object value, ValidationContext validationContext)
          {
              ValidationResult validationResult = ValidationResult.Success;
              try
              {
                  // Using reflection we can get a reference to the other date property, in this example the project start date
                  var containerType = validationContext.ObjectInstance.GetType();
                  var field = containerType.GetProperty(this.otherPropertyName);
                  var extensionValue = field.GetValue(validationContext.ObjectInstance, null);
                  var datatype = extensionValue.GetType();
  
                  //var otherPropertyInfo = validationContext.ObjectInstance.GetType().GetProperty(this.otherPropertyName);
                  if (field == null)
                      return new ValidationResult(String.Format("Unknown property: {0}.", otherPropertyName));
                  // Let's check that otherProperty is of type DateTime as we expect it to be
                  if ((field.PropertyType == typeof(DateTime) || (field.PropertyType.IsGenericType &amp;&amp; field.PropertyType == typeof(Nullable&lt;DateTime&gt;))))
                  {
                      DateTime toValidate = (DateTime)value;
                      DateTime referenceProperty = (DateTime)field.GetValue(validationContext.ObjectInstance, null);
                      // if the end date is lower than the start date, than the validationResult will be set to false and return
                      // a properly formatted error message
                      if (toValidate.CompareTo(referenceProperty) &lt; 1)
                      {
                          validationResult = new ValidationResult(ErrorMessageString);
                      }
                  }
                  else
                  {
                      validationResult = new ValidationResult("An error occurred while validating the property. OtherProperty is not of type DateTime");
                  }
              }
              catch (Exception ex)
              {
                  // Do stuff, i.e. log the exception
                  // Let it go through the upper levels, something bad happened
                  throw ex;
              }
  
              return validationResult;
          }
      }
  </code></pre>
  
  <p>my concern is how to pass start date textbox name from server side to client side which will add some attribute to end date textbox.</p>
  
  <p>so just help me to construct few server side code for <code>GetClientValidationRules</code> function and few code for client side unobtrusive code say for example <code>$.validator.unobtrusive.adapters.add and $.validator.addMethod</code> anyone can help me like how to achieve it. thanks</p>
  
  <h2>UPDATE</h2>
  
  <p>i try to solve it this way but my client side js is not triggering. here is my revised code.</p>
  
  <h2>Model code</h2>
  
  <pre><code>public class DateValTest
  {
      [Display(Name = "Start Date")]
      [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
      public DateTime? StartDate { get; set; }
  
      [Display(Name = "End Date")]
      [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
      [MyDate(ErrorMessage = "Back date entry not allowed")]
      [DateGreaterThanAttribute(otherPropertyName = "StartDate", ErrorMessage = "End date must be greater than start date")]
      public DateTime?  EndDate { get; set; }
  }
  </code></pre>
  
  <h2>my attribute related code</h2>
  
  <pre><code> public class DateGreaterThanAttribute : ValidationAttribute, IClientValidatable
      {
          public string otherPropertyName;
          public DateGreaterThanAttribute() { }
          public DateGreaterThanAttribute(string otherPropertyName, string errorMessage)
              : base(errorMessage)
          {
              this.otherPropertyName = otherPropertyName;
          }
  
          protected override ValidationResult IsValid(object value, ValidationContext validationContext)
          {
              ValidationResult validationResult = ValidationResult.Success;
              try
              {
                  // Using reflection we can get a reference to the other date property, in this example the project start date
                  var containerType = validationContext.ObjectInstance.GetType();
                  var field = containerType.GetProperty(this.otherPropertyName);
                  var extensionValue = field.GetValue(validationContext.ObjectInstance, null);
                  var datatype = extensionValue.GetType();
  
                  //var otherPropertyInfo = validationContext.ObjectInstance.GetType().GetProperty(this.otherPropertyName);
                  if (field == null)
                      return new ValidationResult(String.Format("Unknown property: {0}.", otherPropertyName));
                  // Let's check that otherProperty is of type DateTime as we expect it to be
                  if ((field.PropertyType == typeof(DateTime) || (field.PropertyType.IsGenericType &amp;&amp; field.PropertyType == typeof(Nullable&lt;DateTime&gt;))))
                  {
                      DateTime toValidate = (DateTime)value;
                      DateTime referenceProperty = (DateTime)field.GetValue(validationContext.ObjectInstance, null);
                      // if the end date is lower than the start date, than the validationResult will be set to false and return
                      // a properly formatted error message
                      if (toValidate.CompareTo(referenceProperty) &lt; 1)
                      {
                          validationResult = new ValidationResult(ErrorMessageString);
                      }
                  }
                  else
                  {
                      validationResult = new ValidationResult("An error occurred while validating the property. OtherProperty is not of type DateTime");
                  }
              }
              catch (Exception ex)
              {
                  // Do stuff, i.e. log the exception
                  // Let it go through the upper levels, something bad happened
                  throw ex;
              }
  
              return validationResult;
          }
  
          public IEnumerable&lt;ModelClientValidationRule&gt; GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
          {
              var rule = new ModelClientValidationRule
              {
                  ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
                  ValidationType = "isgreater",
              };
              rule.ValidationParameters.Add("otherproperty", otherPropertyName);
              yield return rule;
          }
      }
  </code></pre>
  
  <h2>my js code</h2>
  
  <pre><code>$.validator.unobtrusive.adapters.add('isgreater', ['otherproperty'], function (options) {
      options.rules['isgreater'] = { isgreater: options.params.otherproperty };
      options.messages['isgreater'] = options.message;
  });
  
  $.validator.addMethod("isgreater", function (value, element, param) {
      alert('pop' + params.otherproperty);
      var otherProp = $('#' + params.otherproperty);
      var date = new Date(value);
      if (otherProp.val() != '')
      {
          return date &gt;= minDate;
      }
      return true;
  });
  </code></pre>
  
  <p>My end date textbox html look like as following</p>
  
  <pre><code>&lt;input type="date" 
  value="03/16/2016" 
  name="EndDate" 
  id="EndDate" 
  data-val-restrictbackdates-mindate="03/16/2016 00:00:00" 
  data-val-restrictbackdates="Back date entry not allowed" 
  
  data-val-isgreater-otherproperty="StartDate" 
  data-val-isgreater="End date must be greater than start date" 
  data-val-date="The field End Date must be a date." 
  data-val="true" 
  class="input-validation-error form-control text-box single-line"&gt;
  </code></pre>
  
  <p><strong>the problem is my js code is not firing.....could not capture the area what is the mistake in js code. need some hint or help. thanks</strong></p>
  
  <h2>I solve it...Updated code here</h2>
  
  <h2>Model code</h2>
  
  <pre><code>public class DateValTest
  {
      [Display(Name = "Start Date")]
      [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
      public DateTime? StartDate { get; set; }
  
      [Display(Name = "End Date")]
      [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
      [MyDate(ErrorMessage = "Back date entry not allowed")]
      [DateGreaterThanAttribute(otherPropertyName = "StartDate", ErrorMessage = "End date must be greater than start date")]
      public DateTime?  EndDate { get; set; }
  }
  </code></pre>
  
  <h2>Custom attribute related class code</h2>
  
  <pre><code>public class MyDateAttribute : ValidationAttribute, IClientValidatable
  {
      private DateTime _MinDate;
  
      public MyDateAttribute()
      {
          _MinDate = DateTime.Today;
      }
  
      protected override ValidationResult IsValid(object value, ValidationContext validationContext)
      {
          DateTime _EndDat = DateTime.Parse(value.ToString(), CultureInfo.InvariantCulture);
          DateTime _CurDate = DateTime.Today;
  
          int cmp = _EndDat.CompareTo(_CurDate);
          if (cmp &gt; 0)
          {
              // date1 is greater means date1 is comes after date2
              return ValidationResult.Success;
          }
          else if (cmp &lt; 0)
          {
              // date2 is greater means date1 is comes after date1
              return new ValidationResult(ErrorMessage);
          }
          else
          {
              // date1 is same as date2
              return ValidationResult.Success;
          }
      }
  
      public IEnumerable&lt;ModelClientValidationRule&gt; GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
      {
          var rule = new ModelClientValidationRule
          {
              ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
              ValidationType = "restrictbackdates",
          };
          rule.ValidationParameters.Add("mindate", _MinDate);
          yield return rule;
      }
  }
  
  public class DateGreaterThanAttribute : ValidationAttribute, IClientValidatable
  {
      public string otherPropertyName;
      public DateGreaterThanAttribute() { }
      public DateGreaterThanAttribute(string otherPropertyName, string errorMessage)
          : base(errorMessage)
      {
          this.otherPropertyName = otherPropertyName;
      }
  
      protected override ValidationResult IsValid(object value, ValidationContext validationContext)
      {
          ValidationResult validationResult = ValidationResult.Success;
          try
          {
              // Using reflection we can get a reference to the other date property, in this example the project start date
              var containerType = validationContext.ObjectInstance.GetType();
              var field = containerType.GetProperty(this.otherPropertyName);
              var extensionValue = field.GetValue(validationContext.ObjectInstance, null);
              if(extensionValue==null)
              {
                  //validationResult = new ValidationResult("Start Date is empty");
                  return validationResult;
              }
              var datatype = extensionValue.GetType();
  
              //var otherPropertyInfo = validationContext.ObjectInstance.GetType().GetProperty(this.otherPropertyName);
              if (field == null)
                  return new ValidationResult(String.Format("Unknown property: {0}.", otherPropertyName));
              // Let's check that otherProperty is of type DateTime as we expect it to be
              if ((field.PropertyType == typeof(DateTime) || (field.PropertyType.IsGenericType &amp;&amp; field.PropertyType == typeof(Nullable&lt;DateTime&gt;))))
              {
                  DateTime toValidate = (DateTime)value;
                  DateTime referenceProperty = (DateTime)field.GetValue(validationContext.ObjectInstance, null);
                  // if the end date is lower than the start date, than the validationResult will be set to false and return
                  // a properly formatted error message
                  if (toValidate.CompareTo(referenceProperty) &lt; 1)
                  {
                      validationResult = new ValidationResult(ErrorMessageString);
                  }
              }
              else
              {
                  validationResult = new ValidationResult("An error occurred while validating the property. OtherProperty is not of type DateTime");
              }
          }
          catch (Exception ex)
          {
              // Do stuff, i.e. log the exception
              // Let it go through the upper levels, something bad happened
              throw ex;
          }
  
          return validationResult;
      }
  
      public IEnumerable&lt;ModelClientValidationRule&gt; GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
      {
          var rule = new ModelClientValidationRule
          {
              ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
              ValidationType = "isgreater",
          };
          rule.ValidationParameters.Add("otherproperty", otherPropertyName);
          yield return rule;
      }
  }
  </code></pre>
  
  <h2>View code with js</h2>
  
  <pre><code>@model AuthTest.Models.DateValTest
  @{
      ViewBag.Title = "Index";
  }
  &lt;h2&gt;Index&lt;/h2&gt;
  
  @using (Html.BeginForm()) 
  {
      @Html.AntiForgeryToken()
  
      &lt;div class="form-horizontal"&gt;
          &lt;h4&gt;DateValTest&lt;/h4&gt;
          &lt;hr /&gt;
          @Html.ValidationSummary(true, "", new { @class = "text-danger" })
          &lt;div class="form-group"&gt;
              @Html.LabelFor(model =&gt; model.StartDate, htmlAttributes: new { @class = "control-label col-md-2" })
              &lt;div class="col-md-10"&gt;
                  @Html.EditorFor(model =&gt; model.StartDate, new { htmlAttributes = new { @class = "form-control" } })
                  @Html.ValidationMessageFor(model =&gt; model.StartDate, "", new { @class = "text-danger" })
              &lt;/div&gt;
          &lt;/div&gt;
  
          &lt;div class="form-group"&gt;
              @Html.LabelFor(model =&gt; model.EndDate, htmlAttributes: new { @class = "control-label col-md-2" })
              &lt;div class="col-md-10"&gt;
                  @Html.EditorFor(model =&gt; model.EndDate, new { htmlAttributes = new { @class = "form-control" } })
                  @Html.ValidationMessageFor(model =&gt; model.EndDate, "", new { @class = "text-danger" })
              &lt;/div&gt;
          &lt;/div&gt;
  
          &lt;div class="form-group"&gt;
              &lt;div class="col-md-offset-2 col-md-10"&gt;
                  &lt;input type="submit" value="Create" class="btn btn-default" /&gt;
              &lt;/div&gt;
          &lt;/div&gt;
      &lt;/div&gt;
  }
  
  &lt;div&gt;
      @Html.ActionLink("Back to List", "Index")
  &lt;/div&gt;
  
  @section Scripts {
      @Scripts.Render("~/bundles/jqueryval")
  &lt;script src="https://cdn.jsdelivr.net/momentjs/2.12.0/moment.min.js"&gt;&lt;/script&gt;
  
  &lt;script type="text/javascript"&gt;
      $.validator.unobtrusive.adapters.add('restrictbackdates', ['mindate'], function (options) {
          options.rules['restrictbackdates'] = { mindate: options.params.mindate };
          options.messages['restrictbackdates'] = options.message;
      });
  
      $.validator.addMethod("restrictbackdates", function (value, element, param) {
          var date = new Date(value);
          var minDate = new Date(param.mindate);
          return date &gt;= minDate;
      });
  
      $.validator.unobtrusive.adapters.add('isgreater', ['otherproperty'], function (options) {
          options.rules['isgreater'] = { otherproperty: options.params.otherproperty };
          options.messages['isgreater'] = options.message;
      });
  
      $.validator.addMethod("isgreater", function (value, element, param) {
          var otherProp = $('#' + param.otherproperty);
          if (otherProp.val() != '') {
              var StartDate = new Date(moment(otherProp.val(), 'MM/DD/YYYY'));
  
              var Enddate = new Date(value);
              if (StartDate != '') {
                  return Enddate &gt;= StartDate;
              }
          }
          return true;
      });
  &lt;/script&gt;
  
  }
  </code></pre>