Passing the ErrorMessage for clientside validation
Since there is no way to validate a property (with unobtrusive clientside validation) using multiple regex patterns (because validation type has to be unique) i decided to extend FluentValidation so i can do the following.
RuleFor(x => x.Name).NotEmpty().WithMessage("Name is required")
.Length(3, 20).WithMessage("Name must contain between 3 and 20 characters")
.Match(@"^[A-Z]").WithMessage("Name has to start with an uppercase letter")
.Match(@"^[a-zA-Z0-9_\-\.]*$").WithMessage("Name can only contain: a-z 0-9 _ - .")
.Match(@"[a-z0-9]$").WithMessage("Name has to end with a lowercase letter or digit")
.NotMatch(@"[_\-\.]{2,}").WithMessage("Name cannot contain consecutive non-alphanumeric characters");
The last thing i need to figure out is how to pass the errormessage which is set using WithMessage()
via GetClientValidationRules()
so it ends up in the "data-val-customregex[SOMEFANCYSTRINGHERETOMAKEITUNIQUE]" attribute on the input element.
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) {
var rule = new ModelClientValidationRule();
rule.ErrorMessage = [INSERT ERRORMESSAGE HERE];
rule.ValidationType = "customregex" + StringFunctions.RandomLetters(6);
rule.ValidationParameters.Add("pattern", pattern);
开发者_StackOverflow yield return rule;
}
I've been looking at the FluentValidation sourcecode, but couldn't figure it out. Anyone got any ideas?I've been discussing how to do this with Jeremy Skinner (the creator of Fluent Validation) at
http://fluentvalidation.codeplex.com/discussions/253505
He was kind enough to write a complete example.
Update
Here is the code we came up with:
First the extensions, for both Match and NotMatch.
public static class Extensions
{
public static IRuleBuilderOptions<T, string> Match<T>(this IRuleBuilder<T, string> ruleBuilder, string expression)
{
return ruleBuilder.SetValidator(new MatchValidator(expression));
}
public static IRuleBuilderOptions<T, string> NotMatch<T>(this IRuleBuilder<T, string> ruleBuilder, string expression) {
return ruleBuilder.SetValidator(new MatchValidator(expression, false));
}
}
The used interface for the validator
public interface IMatchValidator : IPropertyValidator
{
string Expression { get; }
bool MustMatch { get; }
}
The actual validator:
public class MatchValidator : PropertyValidator, IMatchValidator
{
string expression;
bool mustMatch;
public MatchValidator(string expression, bool mustMatch = true)
: base(string.Format("The value {0} match with the given expression, while it {1}.", mustMatch ? "did not" : "did", mustMatch ? "should" : "should not"))
{
this.expression = expression;
this.mustMatch = mustMatch;
}
protected override bool IsValid(PropertyValidatorContext context)
{
return context.PropertyValue == null ||
context.PropertyValue.ToString() == string.Empty ||
Regex.IsMatch(context.PropertyValue.ToString(), expression) == mustMatch;
}
public string Expression
{
get { return expression; }
}
public bool MustMatch {
get { return mustMatch; }
}
}
The adaptor to register the validator:
public class MatchValidatorAdaptor : FluentValidationPropertyValidator
{
public MatchValidatorAdaptor(ModelMetadata metadata, ControllerContext controllerContext, PropertyRule rule, IPropertyValidator validator)
: base(metadata, controllerContext, rule, validator)
{
}
IMatchValidator MatchValidator
{
get { return (IMatchValidator)Validator; }
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var formatter = new MessageFormatter().AppendPropertyName(Rule.PropertyDescription);
string errorMessage = formatter.BuildMessage(Validator.ErrorMessageSource.GetString());
yield return new ModelClientValidationMatchRule(MatchValidator.Expression, MatchValidator.MustMatch, errorMessage);
}
}
And finally where the magic happens:
public class ModelClientValidationMatchRule : ModelClientValidationRule
{
public ModelClientValidationMatchRule(string expression, bool mustMatch, string errorMessage)
{
if (mustMatch)
base.ValidationType = "match";
else
base.ValidationType = "notmatch";
base.ValidationType += StringFunctions.RandomLetters(6);
base.ErrorMessage = errorMessage;
base.ValidationParameters.Add("expression", expression);
}
}
Update 2:
Javascript to wireup jQuery.validator:
(function ($) {
function attachMatchValidator(name, mustMatch) {
$.validator.addMethod(name, function (val, element, expression) {
var rg = new RegExp(expression, "gi");
return (rg.test(val) == mustMatch);
});
$.validator.unobtrusive.adapters.addSingleVal(name, "expression");
}
$("input[type=text]").each(function () {
$.each(this.attributes, function (i, attribute) {
if (attribute.name.length == 20 && attribute.name.substring(0, 14) == "data-val-match")
attachMatchValidator(attribute.name.substring(9, 20), true);
if (attribute.name.length == 23 && attribute.name.substring(0, 17) == "data-val-notmatch")
attachMatchValidator(attribute.name.substring(9, 23), false);
});
});
} (jQuery));
A little off topic, but maybe helpful. Regex are pretty powerful, have you considered combining all the rules in one regex? I think that's why the attributes providing regex validation usually don't allow multiple instances per property.
So for your example, your regex would be:
"^[A-Z]([a-zA-Z0-9][_\-\.]{0,1}[a-zA-Z0-9]*)*[a-z0-9]$"
And a handy place to test it: http://derekslager.com/blog/posts/2007/09/a-better-dotnet-regular-expression-tester.ashx
精彩评论