Question

I am new to mvc 3.0 and jquery. i am trying to validate the 'date' both at client side and server side using customized validation attributes. It is working fine at server side, but am not able to make it work at client side.

am using mvc 3.0, jquery, IE 7.0. do we need to register any thing in global.ascx in MVC 3.0?

Please let me know where I am wrong. TIA.

Here is my code:

Validation Attribute

 public class FutureDateAttribute : ValidationAttribute, IClientValidatable
 {
        private const string DateFormat = "mm/dd/yyyy";
        private const string DefaultErrorMessage = "'{0}' must be a date between {1:d} and current date.";

        public DateTime Min { get; set; }
        public DateTime Max { get; set; }

        public FutureDateAttribute(string min)
            : base(DefaultErrorMessage)
        {
            Min = ParseDate(min);
            Max = DateTime.Now;
        }

        public override bool IsValid(object value)
        {
            if (value == null || !(value is DateTime))
            { return true; }
            DateTime dateValue = (DateTime)value;
            return Min <= dateValue && dateValue <= Max;
        }

        private static DateTime ParseDate(string dateValue)
        {
            return DateTime.ParseExact(dateValue, DateFormat, System.Globalization.CultureInfo.InvariantCulture);
        }

        public override string FormatErrorMessage(string name)
        {
            return String.Format(System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Min);
        }

        public class ModelClientValidationFutureDateRule : ModelClientValidationRule
        {
            public ModelClientValidationFutureDateRule(string errorMessage,
                DateTime min)
            {
                ErrorMessage = errorMessage;
                ValidationType = "futuredate";
                ValidationParameters["min"] = min.ToString("mm/dd/yyyy");
                ValidationParameters["max"] = DateTime.Now.ToString("mm/dd/yyyy");
            }
        }

        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationFutureDateRule("Error message goes here", this.Min);
            yield return rule;
        }

jquery

(function ($) {
$.validator.addMethod('futuredate', function (value, element, param) {
    if (!value) return false;
    var min = $(param.min).val();
    var max = $(param.max).val();
    if (value < min || value > max) {
        return false;
    }
    return true;
});

$.validator.unobtrusive.adapters.add(
  'futuredate', ['min', 'max'],
  function (options) {
      var params = {
          min: options.params.min,
          max: options.params.max
      };

      options.rules['futuredate'] = params;
      if (options.message) {
          options.messages['futuredate'] = options.message;
      }
  });
} (jQuery)); 

References

<script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
 <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
 ///reference path="jquery-1.5.1.min.js" />
 ///reference path="jquery.validate.js" />
 ///reference path="jquery-ui-1.8.11.js" />
  ///reference path="jquery.validate.unobtrusive.min.js" />
 ///reference path="jquery.validate-vsdoc.js" />

Model:

[DisplayName("Assigned Date :")]
    [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
    [Required(ErrorMessage = "Assigned Date is required")]
    [DataType(DataType.Date)]
    [FutureDate("12/31/1899", ErrorMessage = "'{0}' must be a date between {1:d} and current date.")]
    public DateTime? AssignedDate { get; set; }
Was it helpful?

Solution

There are a couple of issues I can see with your code. The first one:

ValidationParameters["min"] = min.ToString("mm/dd/yyyy");
ValidationParameters["max"] = DateTime.Now.ToString("mm/dd/yyyy");

must really be:

ValidationParameters["min"] = min.ToString("MM/dd/yyyy");
ValidationParameters["max"] = DateTime.Now.ToString("MM/dd/yyyy");

because mm means minutes not months.

Same remark for:

private const string DateFormat = "mm/dd/yyyy";

which must be:

private const string DateFormat = "MM/dd/yyyy";

On the client side you have a couple of issues as well. In your futuredate validation method you seem to be doing var min = $(param.min).val(); which translates to var min = $('12/31/1899').val(); which obviously doesn't make much sense. You will have to parse those values into javascript Date instances before being able to compare them.

So here's what I would suggest you:

@model MyViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
    (function ($) {
        var parseDate = function (str) {
            var m = str.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
            return (m) ? new Date(m[3], m[1] - 1, m[2]) : null;
        };

        $.validator.addMethod('futuredate', function (value, element, param) {
            if (!value) return false;

            var min = parseDate(param.min);
            var max = parseDate(param.max);
            var current = parseDate(value);

            if (min == null || max == null || current == null) {
                return false;
            }

            return (current >= min && current <= max);
        });

        $.validator.unobtrusive.adapters.add('futuredate', ['min', 'max'], function (options) {
            var params = {
                min: options.params.min,
                max: options.params.max
            };

            options.rules['futuredate'] = params;
            if (options.message) {
                options.messages['futuredate'] = options.message;
            }
        });
    } (jQuery));
</script>

@using (Html.BeginForm())
{
    @Html.LabelFor(x => x.AssignedDate)
    @Html.EditorFor(x => x.AssignedDate)
    @Html.ValidationMessageFor(x => x.AssignedDate)
    <button type="submit">OK</button>
}

and here's the full code of the validation attribute I have used for my test case:

public class FutureDateAttribute : ValidationAttribute, IClientValidatable
{
    private const string DateFormat = "MM/dd/yyyy";
    private const string DefaultErrorMessage = "'{0}' must be a date between {1:d} and current date.";

    public DateTime Min { get; set; }
    public DateTime Max { get; set; }

    public FutureDateAttribute(string min)
        : base(DefaultErrorMessage)
    {
        Min = ParseDate(min);
        Max = DateTime.Now;
    }

    public override bool IsValid(object value)
    {
        if (value == null || !(value is DateTime))
        { return true; }
        DateTime dateValue = (DateTime)value;
        return Min <= dateValue && dateValue <= Max;
    }

    private static DateTime ParseDate(string dateValue)
    {
        return DateTime.ParseExact(dateValue, DateFormat, System.Globalization.CultureInfo.InvariantCulture);
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Min);
    }

    public class ModelClientValidationFutureDateRule : ModelClientValidationRule
    {
        public ModelClientValidationFutureDateRule(string errorMessage,
            DateTime min)
        {
            ErrorMessage = errorMessage;
            ValidationType = "futuredate";
            ValidationParameters["min"] = min.ToString("MM/dd/yyyy");
            ValidationParameters["max"] = DateTime.Now.ToString("MM/dd/yyyy");
        }
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationFutureDateRule("Error message goes here", this.Min);
        yield return rule;
    }
}

And the model:

public class MyViewModel
{
    [DisplayName("Assigned Date :")]
    [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
    [Required(ErrorMessage = "Assigned Date is required")]
    [DataType(DataType.Date)]
    [FutureDate("12/31/1899", ErrorMessage = "'{0}' must be a date between {1:d} and current date.")]
    public DateTime? AssignedDate { get; set; }
}

and the controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel
        {
            AssignedDate = DateTime.Now.AddDays(2)
        });
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top