How to validate Date in ClientSide using FluentValidation?

14,431

Trick using Greater Then Or Equal To Validator. Works for me.

Global.asax - Application Start Event

FluentValidationModelValidatorProvider.Configure(x =>
{
    x.Add(typeof(GreaterThanOrEqualValidator), 
            (metadata, Context, rule, validator) => 
                new LessThanOrEqualToFluentValidationPropertyValidator
                (
                    metadata, Context, rule, validator
                )
            );
});

Model

[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
    [Display(Name = "Start date")]
    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", 
                                                  ApplyFormatInEditMode = true)]
    public DateTime StartDate { get; set; }

    [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", 
                                                  ApplyFormatInEditMode = true)]
    public DateTime DateToCompareAgainst { get; set; }
}

Rule

public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
    public MyViewModelValidator()
    {
        RuleFor(x => x.StartDate)
            .GreaterThanOrEqualTo(x => x.DateToCompareAgainst)
            .WithMessage("Invalid start date");
    }
}

FluentValidationPropertyValidator

public class GreaterThenOrEqualTo : FluentValidationPropertyValidator
{
    public GreaterThenOrEqualTo(ModelMetadata metadata, 
                                ControllerContext controllerContext, 
                                PropertyRule rule, 
                                IPropertyValidator validator)
        : base(metadata, controllerContext, rule, validator)
    {
    }

    public override IEnumerable<ModelClientValidationRule> 
                                                    GetClientValidationRules()
    {
        if (!this.ShouldGenerateClientSideRules())
        {
            yield break;
        }

        var validator = Validator as GreaterThanOrEqualValidator;

        var errorMessage = new MessageFormatter()
            .AppendPropertyName(this.Rule.GetDisplayName())
            .BuildMessage(validator.ErrorMessageSource.GetString());

        var rule = new ModelClientValidationRule{
            ErrorMessage = errorMessage,
            ValidationType = "greaterthanorequaldate"};
        rule.ValidationParameters["other"] = 
            CompareAttribute.FormatPropertyForClientValidation(
                validator.MemberToCompare.Name);
        yield return rule;
    }
}

Controller Action Method

public ActionResult Index()
{
    var model = new MyViewModel
    {
        StartDate = DateTime.Now.AddDays(2),
        DateToCompareAgainst = default(DateTime)  //Default Date
    };
    return View(model);
}
[HttpPost]
public ActionResult Index(Practise.Areas.FluentVal.Models.MyViewModel p)
{
    return View(p);
}

View

@using (Html.BeginForm("Index", "Person", FormMethod.Post, 
                                                new { id = "FormSubmit" }))
{   
    @Html.Hidden("DateToCompareAgainst", Model.DateToCompareAgainst);      
    @Html.LabelFor(x => x.StartDate)
    @Html.EditorFor(x => x.StartDate)
    @Html.ValidationMessageFor(x => x.StartDate)
    <button type="submit">
        OK</button>
}

Script

<script src="jquery-1.4.1.min.js" type="text/javascript"></script>
<script src="jquery.validate.js" type="text/javascript"></script>
<script src="jquery.validate.unobtrusive.js" type="text/javascript"></script>
<script type="text/javascript">
    (function ($) {
        $.validator.unobtrusive.adapters.add('greaterthanorequaldate', 
                                             ['other'], function (options) {
            var getModelPrefix = function (fieldName) {
                return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
            };

            var appendModelPrefix = function (value, prefix) {
                if (value.indexOf("*.") === 0) {
                    value = value.replace("*.", prefix);
                }
                return value;
            }

            var prefix          = getModelPrefix(options.element.name),
                other           = options.params.other,
                fullOtherName   = appendModelPrefix(other, prefix),
            element = $(options.form).find(":input[name=" + fullOtherName + 
                                                        "]")[0];

            options.rules['greaterthanorequaldate'] = element;
            if (options.message != null) {
                options.messages['greaterthanorequaldate'] = options.message;
            }
        });

        $.validator.addMethod('greaterthanorequaldate', 
                               function (value, element, params) {
            var date = new Date(value);
            var dateToCompareAgainst = new Date($(params).val());

            if (isNaN(date.getTime()) || isNaN(dateToCompareAgainst.getTime())) {
                return false;
            }
            return date >= dateToCompareAgainst;
        });

    })(jQuery);
</script>
Share:
14,431
SMC
Author by

SMC

Updated on August 11, 2022

Comments

  • SMC
    SMC over 1 year

    Question

    The below code is working fine Server side and not Client side. Why ?


    When I submit the form, control goes to BeAValidDate function to check the date is valid or not. Is there any way to Validate the date without going to server using Fluent Validation?

    Scripts

    <script src="jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="jquery.validate.js" type="text/javascript"></script>
    <script src="jquery.validate.unobtrusive.js" type="text/javascript"></script>
    

    Model

    public class PersonValidator : AbstractValidator<Person>
    {
        public PersonValidator()
        {
            RuleFor(x => x.FromDate)
                .NotEmpty()
                .WithMessage("Date is required!")
                .Must(BeAValidDate)
                .WithMessage("Invalid Date");
        }
    
        private bool BeAValidDate(String value)
        {
            DateTime date;
            return DateTime.TryParse(value, out date);
        }
    }
    

    Controller

    public class PersonController : Controller
    {
        public ActionResult Index()
        {
           return View(new Person { FromDate = DateTime.Now.AddDays(2).ToString()});
        }
    
        [HttpPost]
        public ActionResult Index(Person p)
        {
            return View(p);
        }
    }
    

    View

    @using (Html.BeginForm("Index", "Person", FormMethod.Post))
    {   
        @Html.LabelFor(x => x.FromDate)
        @Html.EditorFor(x => x.FromDate)
        @Html.ValidationMessageFor(x => x.FromDate)
    
        <input type="submit" name="Submit" value="Submit" />
    }
    
  • SMC
    SMC over 10 years
    This is called Data Annotation not Fluent Validation. Right ?
  • SMC
    SMC over 10 years
    thanks for the answer. can you please share your experience for mvc3 ?
  • Francesco Abbruzzese
    Francesco Abbruzzese over 10 years
    The way to add client side validation to fluent validation is to implement a validator, so you can't use simply .Must, since it doesn't give way to add validation. It is enough that your validator implements the IClientValidatable interface. The fluent validation project explain how to create a custom validator here: fluentvalidation.codeplex.com/… In order to add client side validation your custom validator must implement the IClientValidatable.
  • Francesco Abbruzzese
    Francesco Abbruzzese over 10 years
    The way to impement IClientValidatable is exactly the same as with DataAnnotations. Unluckly for this reason it is not explained in the fluent validation web site. See here for a simple way to implement all client validation stuffs:stackoverflow.com/questions/5154231/… It is for DataAnnotations, bur as I said IT IS THE SAME. On the .Net side your validator must implement the same IClientValidatable class, and on the javascript side stuffs are exactly the same.
  • Francesco Abbruzzese
    Francesco Abbruzzese over 10 years
    That said the check for data validity SHOULD NOT be implemented with a rule applied to each DateTime, but should be applied automatically to all DateTime as Mvc4 do. The way to do this is to substitute the ClientDataTypeModelValidatorProvider of Mvc 3 that signals just ill formatted numbers, with one that validates also dates (like the one of Mvc 4). I have the code that solve the problem, but is not so self contained to be inserted here, if you are interested I can provide you that code.
  • Francesco Abbruzzese
    Francesco Abbruzzese over 10 years
    Sample code of what? A ClientDataTypeModelValidatorProvider that is able to signall automatically all hill formatted dates that is what you need in your example, or of how to Implement the IClientValidatable interfaces + associated javascript stuff?
  • SMC
    SMC over 10 years
    Yes. Need to correct my code so that it can start to work on client side to check valid date.
  • MsBao
    MsBao about 8 years
    All that to compare a number.