Fluent validation custom validation rules

38,204

Solution 1

1) how to use my service for validation?

You could use the Must rule:

RuleFor(x => x.Email)
    .NotEmpty()
    .WithMessage("Email is required.")
    .EmailAddress()
    .WithMessage("Invalid email format.")
    .Must(userService.IsEmailUnique)
    .WithMessage("Email already taken");

2) is it possible to register multiple Regular Expression Rules with different error messages? will it work on client side? (if no, how to create custom validation logic for it?)

No, you can have only one validation type per property

if no, how to create custom validation logic for it?

You could use the Must rule:

RuleFor(x => x.Password)
    .Must(password => SomeMethodContainingCustomLogicThatMustReturnBoolean(password))
    .WithMessage("Sorry password didn't satisfy the custom logic");

3) is validation on server side will work automatically before model pass in action method, and it is enough to call ModelState.IsValid property, or I need to do something more?

Yes, absolutely. Your controller action could look like this:

[HttpPost]
public ActionResult Register(RegisterModel model)
{
    if (!ModelState.IsValid)
    {
        // validation failed => redisplay the view so that the user
        // can fix his errors
        return View(model);
    }

    // at this stage the model is valid => process it
    ...
    return RedirectToAction("Success");
}

UPDATE:

4) is it possible to access to all properties of model when validate some property? (for example I want to compare Password and ConfirmPassword when register)

Yes, of course:

RuleFor(x => x.ConfirmPassword)
    .Equal(x => x.Password)
    .WithMessage("Passwords do not match");

Solution 2

a nicer variant is to use a RuleBuilderExtension:

public static class RuleBuilderExtensions
{
    public static IRuleBuilder<T, string> Password<T>(this IRuleBuilder<T, string> ruleBuilder, int minimumLength = 14)
    {
        var options = ruleBuilder
            .NotEmpty().WithMessage(ErrorMessages.PasswordEmpty)
            .MinimumLength(minimumLength).WithMessage(ErrorMessages.PasswordLength)
            .Matches("[A-Z]").WithMessage(ErrorMessages.PasswordUppercaseLetter)
            .Matches("[a-z]").WithMessage(ErrorMessages.PasswordLowercaseLetter)
            .Matches("[0-9]").WithMessage(ErrorMessages.PasswordDigit)
            .Matches("[^a-zA-Z0-9]").WithMessage(ErrorMessages.PasswordSpecialCharacter);
        return options;
    }

This way it gets trivial to use:

RuleFor(x => x.Password).Password();
Share:
38,204
David Levin
Author by

David Levin

I'm professional C#/.NET developer, mostly focused on ASP.NET Core.

Updated on June 16, 2020

Comments

  • David Levin
    David Levin almost 4 years

    I have model:

    [Validator(typeof(RegisterValidator))]
    public class RegisterModel
    {
        public string Name { get; set; }
    
        public string Email { get; set; }
    
        public string Password { get; set; }
    
        public string ListOfCategoriess { get; set; }
    }
    

    And validator for model:

    public class RegisterValidator:AbstractValidator<RegisterModel>
    {
        public RegisterValidator(IUserService userService)
        {
            RuleFor(x => x.Name).NotEmpty().WithMessage("User name is required.");
            RuleFor(x => x.Email).NotEmpty().WithMessage("Email is required.");
            RuleFor(x => x.Email).EmailAddress().WithMessage("Invalid email format.");
            RuleFor(x => x.Password).NotEmpty().WithMessage("Password is required.");
            RuleFor(x => x.ConfirmPassword).NotEmpty().WithMessage("Please confirm your password.");
        }
    }
    

    I have validator factory, that should resolve dependency:

    public class WindsorValidatorFactory : ValidatorFactoryBase 
    {
        private readonly IKernel kernel;
    
        public WindsorValidatorFactory(IKernel kernel)
        {
            this.kernel = kernel;
        }
    
        public override IValidator CreateInstance(Type validatorType)
        {
            if (validatorType == null)
                throw new Exception("Validator type not found.");
            return (IValidator) kernel.Resolve(validatorType);
        }
    }
    

    I have IUserService, that has methods IsUsernameUnique(string name) and IsEmailUnique(string email)` and want to use it in my validator class (model should be valid only if it have unique username and email).

    1. how to use my service for validation?
    2. is it possible to register multiple Regular Expression Rules with different error messages? will it work on client side? (if no, how to create custom validation logic for it?)
    3. is validation on server side will work automatically before model pass in action method, and it is enough to call ModelState.IsValid property, or I need to do something more? UPDATE
    4. is it possible to access to all properties of model when validate some property? (for example I want to compare Password and ConfirmPassword when register)
  • David Levin
    David Levin about 12 years
    About 2: i want to have multiple regexps and each regexp has own error message. Is it possible?
  • Darin Dimitrov
    Darin Dimitrov about 12 years
    @EvgenyLevin, I have already covered this in my answer: No, you can have only one validation type per property. So you have 2 possibilities: either come up with a single regex that can validate everything or use the Must rule to write some custom logic.
  • David Levin
    David Levin about 12 years
    and I can't write more than one .Must rules for custom logic too? Please look at update (point 4).
  • Darin Dimitrov
    Darin Dimitrov about 12 years
    @EvgenyLevin, I don't understand what the problem is? Can't you simply use the .Equal rule? See my updated answer.
  • David Levin
    David Levin about 12 years
    Equal helps for password and confirm password, but what if I want to validate email+password combination when user try log in? Is it possible to pass both parameters in my service method?
  • Darin Dimitrov
    Darin Dimitrov about 12 years
    Yes, it is possible. You could use the .Must rule: RuleFor(x => x.Password).Must((model, password) => userService.AreEmailAndPasswordValid(model.Email, password)).WithMessage("Invalid email+password combination");.
  • chatay
    chatay almost 3 years
    @MovGP0 when it is used with jquery.validate.js and jquery.validate.unobtrusive.js, somehow it does not hit the second .Matches function.. I try to use it with client-side validation. It is getting stuck with first matches function. Any idea how to fix it?
  • MovGP0
    MovGP0 almost 3 years
    I don't use jquery.validate. The example is for FluentValidation in .NET (ie. Server-Side or via Blazor).