Search code examples
asp.net-mvc-5jquery-validatefluentvalidation

DateTime client-side validation fails due to formatting


I'm using FluentValidation in an MVC5 project without any issues except for dates. My goal is to have GreaterThanOrEqualTo client-side validation working for a few date fields. I know that the current documentation doesn't list this method as supported client-side, but it renders the correct jQuery Validation data attributes.

In my model, I have a property:

[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
[Display(Name = "Credit Decision Due Date")]
public DateTime? DueDate { get; set; }

In my view, I am using standard Razor syntax to draw the control group:

<div class="control-group">
    @Html.LabelFor(m => m.DueDate, new { @class = "control-label text-bold" })
    @Html.TextBoxFor(m => m.DueDate, "{0:MM/dd/yyyy}", new { @class = "span12 date-picker" })
    @Html.ValidationMessageFor(m => m.DueDate)
</div>

In my validator, there's just a simple rule:

RuleFor(m => m.DueDate).GreaterThanOrEqualTo(DateTime.Today);

Note, I have also tried InclusiveBetween, (which is explicitly listed as being supported client-side in the documentation):

RuleFor(r => r.DueDate).InclusiveBetween(DateTime.Today, DateTime.Today.AddYears(1));

The result of the view rendering with GreaterThanOrEqualTo is:

<div class="control-group">
    <label class="control-label text-bold" for="DueDate">Credit Decision Due Date <span class="text-error required-asterisk">*</span></label>
    <input class="span12 date-picker hasDatepicker" data-val="true" data-val-range="'Credit Decision Due Date' must be greater than or equal to '9/27/2018 12:00:00 AM'." data-val-range-max="" data-val-range-min="09/27/2018 00:00:00" data-val-required="'Credit Decision Due Date' must not be empty." id="DueDate" name="DueDate" type="text" value="">
    <span class="field-validation-valid help-block" data-valmsg-for="DueDate" data-valmsg-replace="true"></span>
</div>

Everything looks pretty good so far I'd say, except the validation running client side is misbehaving, almost like it's treating the value as text instead of a Date. For example, a false negative:

enter image description here

And and false positive:

enter image description here

I am not doing anything in the jQuery Datepicker attached to these fields other than forcing the field to validate immediately after picking a date:

$('.date-picker').datepicker({
    changeMonth: true,
    changeYear: true,
    onClose: function () {
        $(this).valid();
    }
});

The entire application uses an "en-US" locale by default. All other validation is working.

I'm using the following library versions:

  • FluentValidation 8
  • jQuery 1.11.3
  • jQuery Validate 1.17
  • jQuery Validate Unobtrusive 3.2.10 (Microsoft)

I know I could write a custom validator that parses the input and range values into JavaScript Date objects before comparing, but it seems like there should be something built-in to get everything to recognize these strings as dates.

What am I missing?


Solution

  • The issue is that the range rule in jquery.validate.js works with numeric values, and if its not numeric, then it makes a string comparison.

    You can override the default behavior by adding your own rule to compare dates. Add the following script after the validate* scripts but not inside $(document).ready

    $.validator.methods.range = function(value, element, param) {
        if ($(element).attr('data-val-date')) {
            var min = $(element).attr('data-val-range-min');
            var max = $(element).attr('data-val-range-max');
            var date = new Date(value).getTime();
            var minDate = new Date(min).getTime() || 0;
            var maxDate = new Date(max).getTime() || 8640000000000000;
            return this.optional(element) || (date >= minDate && date <= maxDate);
        }
        // use the default method
        return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] );
    };
    

    Note that your TextBoxFor() method should be generating a data-val-date="The field Credit Decision Due Date must be a date." attribute. I am not sure if you just omitted that from the resulting html you have shown, or you have some other code that is omitting it. If its not being generated, then you would need to modify the if ($(element).attr('data-val-date')) { line of code, perhaps to use a class name, for example

    if ($(element).hasClass('date-picker')) {
    

    Note also that your

    [DataType(DataType.Date)]
    [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
    

    attributes are not necessary if using TextBoxFor() - those attributes are only respected when using EditorFor(), and if you do use EditorFor(), then the format string should be in ISO format - i.e DataFormatString = "{0:yyyy-MM-dd}" as explained in The specified value does not conform to the required format yyyy-MM-dd