4

I am using FluentValidation in my Asp.Net MVC 4 application. I have already known that some rules are automatically generating attributes for jQuery validation library. And this script library has already known what it must check for example in case of data-rule-required, data-rule-range and so on.

I know that there are some functions in FluentValidation, but these are not include for client-side. For example: .Equal(true). I have checked @DarinDimitrov answer here and implemented this without any problem.

But, I don't want always to create new class which is inherited from FluentValidationPropertyValidator. And we must add this to provider like that in global.asax:

provider.Add(typeof(EqualValidator), (metadata, context, description, validator) => new EqualToValueClientRule(metadata, context, description, validator));

In this case EqualValidator already implemented in FluentValidation. But, what if we have created a validator with When keyword. For example, I have:

this.RuleFor(phone => phone.Digits)
    .Length(7)
        .When(phone => phone.PrefixId == 2)
        .WithMessage("Numbers in 2nd city must contain 7 characters");

this.RuleFor(phone => phone.Digits)
    .Length(7)
        .When(phone => phone.PrefixId > 64)
        .WithMessage("Mobile number must contain 7 characters");

this.RuleFor(phone => phone.Digits)
    .Length(5)
        .When(phone => phone.PrefixId != 2)
        .WithMessage("Numbers in other cities must contain 5 characters")

Of course, I can check this with jQuery/JavaScript without any problem. But, this approach is not good. And in other cases you have to write so much code for generating custom attributes in client side and add new function to adapter. Or, just use jQuery/JavaScript? Or any other thing? May be we can add JavaScript function name to FluentValidationPropertyValidator?

What do you recommend me?

Community
  • 1
  • 1
Farhad Jabiyev
  • 26,014
  • 8
  • 72
  • 98
  • I suppose the best approach is to create PropertyValidator class, that always valid on server-side, register javascript function name, that will be executed on client-side. – David Levin May 06 '15 at 13:34
  • @EvgenyLevin I have made this already. Also, my validator implementing `IClientValidatable` also. It will create validation attributes in client side. So, I can easily use this validator in ay other projects. – Farhad Jabiyev May 06 '15 at 13:42
  • Yes, your validator is good to be reuseable as phone validator, but my idea was to create 'fake' validator that only register client-side function in special attribute, and adapter, that call that js function, and extension to work with rule builder chain. Then calls will look like that: `RuleFor(phone => phone.Digits).Length(7).When(...).WithMessage("...").Length(5).When(...).WithMessage("...").ClientSideRule("allPhoneValidationLogicWithWhenConditionsFunction");` – David Levin May 09 '15 at 10:12
  • @EvgenyLevin I got it. But, where I will create client-side attributes? – Farhad Jabiyev May 09 '15 at 10:17

1 Answers1

3

I have thought a lot and find that the best way is to create new validator which inherits from PropertyValidator and implements IClientValidatable interface. As a result, it will contain server-side validation and will generate unobtrusive attributes as we wish. Then we must need register this new validator in unobtrusive library.

For example, the validator for the rules in my question will be:

public class MustFitToPhonePrefix<TModel, TProperty> : PropertyValidator, IClientValidatable
    {
        private string dependencyElement;

        public MustFitToPhonePrefix(Expression<Func<TModel, TProperty>> expression)
            : base("Format is wrong")
        {
            dependencyElement = (expression.Body as MemberExpression).Member.Name;
        }

        // Server side validation
        protected override bool IsValid(PropertyValidatorContext context)
        {
            // Instance of the class which contains property which must be validated 
            var phone = context.ParentContext.InstanceToValidate as PhoneDetail;

            ...
            // Custom logic
            ...

            // Everything is valid
            return true;
        }

        // Generate jquery unobtrusive attributes
        public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            var rule = new ModelClientValidationRule
            {
                ErrorMessage = this.ErrorMessageSource.GetString(), // default error message
                ValidationType = "fittoprefix" // name of the validatoin which will be used inside unobtrusive library
            };

            rule.ValidationParameters["prefixelement"] = dependencyElement; // html element which includes prefix information
            yield return rule;
        }

And now we can register our client-side validator:

// Will check if the phone number fits to phone prefix
$.validator.addMethod('fittoprefix', function (value, element, params) {
    var parent = getParentPropertyName(element);
    var prefixId = $("#{0}_{1}".format(parent, params.prefixelement)).val();
    var digitsLength = $(element).val().Length;

    ...
    // Custom logic
    ...

    return true;
});

// Registration - Will check if the phone number fits to phone prefix
$.validator.unobtrusive.adapters.add('fittoprefix', ['prefixelement'], function (options) {
    options.rules['fittoprefix'] = options.params;
    if (options.message != null) {
        options.messages['fittoprefix'] = options.message;
    }
});

And at last, we can set our validator:

   this.RuleFor(m => m.Digits)
       .SetValidator(new MustFitToPhonePrefix<PhoneDetail, int>(m => m.PrefixId));
Farhad Jabiyev
  • 26,014
  • 8
  • 72
  • 98