unobtrusive client validation using fluentvalidation and asp.net mvc LessThanOrEqualTo not firing
Solution 1
Neither of the LessThanOrEqualTo
or GreaterThanOrEqualTo
rules are supported by client side validation as explained in the documentation.
This means that if you want to have client side validation for them you will need to write a custom FluentValidationPropertyValidator
and implement the GetClientValidationRules
method which will allow you to register a custom adapter and implement the client side validation logic for it in javascript.
If you are interested on how this could be achieved just ping me and I will provide an example.
Update
As request, I will try to show an example of how one could implement custom client side validation for the LessThanOrEqualTo
rule. It is only a particular case with non-nullable dates. Writing such custom client side validator for all the possible case is of course possible but it will require significantly more efforts.
So we start with a view model and a corresponding validator:
[Validator(typeof(MyViewModelValidator))]
public class MyViewModel
{
[Display(Name = "Start date")]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime StartDate { get; set; }
public DateTime DateToCompareAgainst { get; set; }
}
public class MyViewModelValidator : AbstractValidator<MyViewModel>
{
public MyViewModelValidator()
{
RuleFor(x => x.StartDate)
.LessThanOrEqualTo(x => x.DateToCompareAgainst)
.WithMessage("Invalid start date");
}
}
Then a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
StartDate = DateTime.Now.AddDays(2),
DateToCompareAgainst = DateTime.Now
};
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and a view:
@model MyViewModel
@using (Html.BeginForm())
{
@Html.Hidden("DateToCompareAgainst", Model.DateToCompareAgainst.ToString("yyyy-MM-dd"))
@Html.LabelFor(x => x.StartDate)
@Html.EditorFor(x => x.StartDate)
@Html.ValidationMessageFor(x => x.StartDate)
<button type="submit">OK</button>
}
All this is standard stuff so far. It will work but without client validation.
The first step is to write the FluentValidationPropertyValidator
:
public class LessThanOrEqualToFluentValidationPropertyValidator : FluentValidationPropertyValidator
{
public LessThanOrEqualToFluentValidationPropertyValidator(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 LessThanOrEqualValidator;
var errorMessage = new MessageFormatter()
.AppendPropertyName(this.Rule.GetDisplayName())
.BuildMessage(validator.ErrorMessageSource.GetString());
var rule = new ModelClientValidationRule
{
ErrorMessage = errorMessage,
ValidationType = "lessthanorequaldate"
};
rule.ValidationParameters["other"] = CompareAttribute.FormatPropertyForClientValidation(validator.MemberToCompare.Name);
yield return rule;
}
}
which will be registered in Application_Start
when configuring our FluentValidation provider:
FluentValidationModelValidatorProvider.Configure(x =>
{
x.Add(typeof(LessThanOrEqualValidator), (metadata, context, rule, validator) => new LessThanOrEqualToFluentValidationPropertyValidator(metadata, context, rule, validator));
});
And the last bit is the custom adapter on the client. So we add of course the 2 scripts to our page in order to enable unobtrusive client side validation:
<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>
and the custom adapter:
(function ($) {
$.validator.unobtrusive.adapters.add('lessthanorequaldate', ['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['lessthanorequaldate'] = element;
if (options.message != null) {
options.messages['lessthanorequaldate'] = options.message;
}
});
$.validator.addMethod('lessthanorequaldate', function (value, element, params) {
var parseDate = function (date) {
var m = date.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
return m ? new Date(parseInt(m[1]), parseInt(m[2]) - 1, parseInt(m[3])) : null;
};
var date = parseDate(value);
var dateToCompareAgainst = parseDate($(params).val());
if (isNaN(date.getTime()) || isNaN(dateToCompareAgainst.getTime())) {
return false;
}
return date <= dateToCompareAgainst;
});
})(jQuery);
Solution 2
Darin's example has some obsolete stuff in it, so here is a more updated example that I have that does number comparisons. You can easily tweak it for date comparisons though:
Javascript:
(function ($)
{
$.validator.addMethod("lessthanorequal", function(value, element, param)
{
return this.optional(element) || parseFloat(value) <= parseFloat(param);
}, "Must be less than");
$.validator.unobtrusive.adapters.add("lessthanorequal", ["field"], function (options)
{
options.rules["lessthanorequal"] = options.params.field;
if (options.message) options.messages["lessthanorequal"] = options.message;
});
})(jQuery);
C#
public class LessThanOrEqualPropertyValidator : FluentValidationPropertyValidator
{
public LessThanOrEqualPropertyValidator(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator)
: base(metadata, controllerContext, rule, validator)
{
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
if (!ShouldGenerateClientSideRules()) yield break;
var formatter = new MessageFormatter().AppendPropertyName(Rule.PropertyName);
string message = formatter.BuildMessage(Validator.ErrorMessageSource.GetString());
var rule = new ModelClientValidationRule
{
ValidationType = "lessthanorequal",
ErrorMessage = message
};
rule.ValidationParameters["field"] = ((LessThanOrEqualValidator)Validator).ValueToCompare;
yield return rule;
}
}
Global.asax Application_Start:
FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure(x =>
{
x.Add(typeof(LessThanOrEqualValidator), (metadata, context, description, validator) => new LessThanOrEqualPropertyValidator(metadata, context, description, validator));
});
So now any number rule that uses LessThanOrEqual will be validated client side.
Solution 3
LessThanOrEqualTo
and GreaterThanOrEqualTo
do not support clientside validation out of the box.
However, InclusiveBetween
is supported. So you could use InclusiveBetween
.
Example
RuleFor(x => x.StartDate)
.InclusiveBetween(x.AbsoluteStartDate, x.AbsoluteEndDate)
See the documentation for mor information about supported clientside methods.
Related videos on Youtube
Admin
Updated on July 09, 2022Comments
-
Admin almost 2 years
I have the following rules
the 1st does work using unobtrusive, client side validation, the second does not
any ideas why?
RuleFor(x => x.StartDate) .LessThanOrEqualTo(x => x.EndDate.Value) .WithLocalizedMessage(() => CommonRes.Less_Than_Or_Equal_To, filters => CommonRes.Start_Date, filters => CommonRes.End_Date); RuleFor(x => x.StartDate) .GreaterThanOrEqualTo(x => x.AbsoluteStartDate) .LessThanOrEqualTo(x => x.AbsoluteEndDate) .WithLocalizedMessage(() => CommonRes.Between, filters => CommonRes.Start_Date, filters => filters.AbsoluteStartDate, filters => filters.AbsoluteEndDate);
-
Darin Dimitrov about 12 yearsAre you sure that the first works?
LessThanOrEqualTo
is not one of the rules listed in the documentation as being supported by client validation. Which version of FV are you using? -
Admin about 12 yearsoh man!! it was 'LessThanOrEqualTo' is there any work around for this?
-
-
Darin Dimitrov about 12 years@iwayneo, OK. The first question that I have is whether the property you are comparing against in the LessThanOrEqualTo validator has a corresponding field in the form i.e. are you comparing against a dynamic value (that can be changed by the user in an input field) or is it the value that this property had at the moment the view was rendered?
-
Admin about 12 yearsthe property being validated is a form field - the proerty we are comparing against is a static non-editable field known the moment the view was rendered - we store it in a hidden field in the form
-
Darin Dimitrov about 12 years@iwayneo, you are storing the value to compare against in a hidden field??? Wait a minute. What would prevent an user from using for example FireBug to modify the value of this hidden to whatever date he wants and then entering whatever date he wants in the input field and thus short-circuiting even your server side validation??? That's a huge security threat for your application. How can you possibly compare against a value that comes from the client (hidden field in this case but easily modifiable)?
-
Admin about 12 yearsi can swap it so we don't do it like that that is not an issue. this is an internal app used by internal accounts staff so not really worried about hackers to be honest. but your point is valid and i can skin the cat another way :) how would i move on with this if it were being retrieved another way?
-
Darin Dimitrov about 12 years@iwayneo, please see my updated answer for an example that might give you some ideas. The example is far from complete, as taking into account all the cases would require significantly more efforts. Hopefully it will give you some hints.
-
Admin about 12 yearsthank you! that is spot on - i have some show and tell meetings today but will report back as soon as this is implemented.
-
Gromer over 11 yearsIn your
Application_Start
section of your example, you're registeringtypeof(LessThanOrEqualValidator)
. I'm a bit confused, as you haven't created anything of that type, so where didLessThanOrEqualValidator
come from in this context? How doesLessThanOrEqualToFluentValidationPropertyValidator
play into this? -
Darin Dimitrov over 11 years@Gromer, if you look more carefully in my
Application_Start
method you will notice that I have registered theLessThanOrEqualToFluentValidationPropertyValidator
validator with theLessThanOrEqualValidator
rule. -
Gromer over 11 yearsUgh, didn't scroll to the right enough. My bad, thanks for the reply!!
-
Darin Dimitrov over 10 years@PKKG, probably because you have some javascript error in your code.
-
Willy over 9 yearsI followed your tutorial and when run the application, I've got error 'Object reference not set to an instance of an object' on
validator.MemberToCompare.Name
. I've checked thatvalidator.MemberToCompare
is null -
Willy over 9 yearsClient side validation run well, but the error message is not well-form
'PurchaseDate' must be less than or equal to '{ComparisonValue}'.
-
Willy over 9 yearsThere is also date format problem, I'm using dd/MM/yyyy pattern, for example current date is 20/10/2014. If I fill 15/10/2014, it will display
'PurchaseOrderDate' must be less than or equal to '{ComparisonValue}'.
-
JsonStatham over 8 years@DarinDimitrov do we have to create a new class that inherits from FluentValidationPropertyValidator every time we want to add a new custom clientside validator?
-
Ciaran Gallagher over 5 yearsHi @DarinDimitrov, do you know if it would be possible to adapt this property validator for fluent validations that use the Must() method?
-
Jakub Keller about 5 yearsThis is way too much code to do something as trivial as validation.