'FluentValidator custom Password is stopping at the first rule

I am using FluentValidator in an MVC project to validate view model passwords so this is my class:

namespace example123.Models
{
    public class RegisterModel
    {
        public string Email { get; set; }
        public string Password { get; set; }
        public string ConfirmPassword { get; set; }
    }

    public class RegisterValidator : AbstractValidator<RegisterModel>
    {
        public RegisterValidator()
        {
            RuleFor(x => x.Email)
                .NotNull()
                .NotEmpty()
                .WithMessage("Email is required")
                .EmailAddress()
                .WithMessage("Invalid email format");

            RuleFor(a => a.Password).Password();
        }
    }
}

So I created a custom rule and extended the rule with:

public static class RuleBuilderExtensions
    {
        public static IRuleBuilder<T, string> Password<T>(this IRuleBuilder<T, string> ruleBuilder, int minimumLength = 8)
        {
            var options = ruleBuilder
                .NotEmpty().WithMessage("Required")
                .MinimumLength(minimumLength).WithMessage("Length")
                .Matches("[A-Z]").WithMessage("Uppercase msg")
                .Matches("[a-z]").WithMessage("Lowercase msg")
                .Matches("[0-9]").WithMessage("Digit msg")
                .Matches("[^a-zA-Z0-9]").WithMessage("Sepcial char msg");
            return options;
        }
    }

Rendered Html code:

 <input type="password" class="form-control input-validation-error validate-equalTo-blur" autocomplete="current-password" aria-required="true" data-val="true" data-val-minlength="Password must be at least 6 characters long" data-val-minlength-min="6" data-val-regex="Uppercase" data-val-regex-pattern="[A-Z]" data-val-required="Password is required" id="Password" name="Password" aria-describedby="Password-error" aria-invalid="true">

The view Register.cshtml is using default jQuery-validate implementation that comes in default MVC project. Email is working fine but the password is stopping at the "Uppercase" rule [A-Z] and is not going away, other custom messages are not displayed. Any idea why?



Solution 1:[1]

I copied your code and wrote the following little xUnit test:

[Theory]
[InlineData("hello", false)]
[InlineData("helloworld", false)]
[InlineData("HelloWorld", false)]
[InlineData("HelloWorld1", false)]
[InlineData("HelloWorld1!", true)]
public void TestName(string password, bool expectedValidity)
{
    // Arrange
    var testee = new RegisterValidator();

    // Act
    var result = testee.Validate(new RegisterModel{Email = "[email protected]", Password = password});

    // Assert
    result.IsValid.Should().Be(expectedValidity);
}

All the tests are passing on my machine. So could you please check whether one of the test cases fails on your machine? If not, there must be a problem with the client logic (e. g. missing refresh).

Solution 2:[2]

So I am included a bunch of different options that might meet your needs.

Are you opposed to using the Must method of FluentValidation? If so it can look something like this:

Create a validation method:

private bool ValidatePassword(string pw)
{
    var lowercase = new Regex("[a-z]+");
    var uppercase = new Regex("[A-Z]+");
    var digit = new Regex("(\\d)+");
    var symbol = new Regex("(\\W)+");

    return (lowercase.IsMatch(pw) && uppercase.IsMatch(pw) && digit.IsMatch(pw) && symbol.IsMatch(pw));
}

Then create the rule where you did your email one.

RuleFor(x => x.Password)
            .Length(5, 15)
            .Must(HasValidPassword);
    }

And if you have a confirm password field you can do this too:

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

I believe the main issue is that you are doing this in an extensions which fluentvalidation can consider custom and not built-in, so it might run into some problems.

Only other thing I noticed is you could try changing:

.Matches("[A-Z]").WithMessage("Uppercase msg")

to

.Matches(@"[A-Z]+").WithMessage("Your password must contain at least one uppercase letter.")
.Matches(@"[a-z]+").WithMessage("Your password must contain at least one lowercase letter.")
 .Matches(@"[0-9]+").WithMessage("Your password must contain at least one number.")
 .Matches(@"[\!\?\*\.]+").WithMessage("Your password must contain at least one (!? *.).");

I added the @ and + for regex regular expression. That might help you too.

Not sure if fluentvalidation cares about the plus sign, but try taking it out of the extension method and doing a normal rule like you did for email and see if that solves it.

If you are trying to do client-side validation then I am pretty sure this won't work. The way you have it currently I believe is for server-side validation and not client. You could just use jQuery directly in the client too and that might meet your need for clien-side. Here is a quick example:

jQuery.validator.addMethod("passwordCheck",
        function(value, element, param) {
            if (this.optional(element)) {
                return true;
            } else if (!/[A-Z]/.test(value)) {
                return false;
            } else if (!/[a-z]/.test(value)) {
                return false;
            } else if (!/[0-9]/.test(value)) {
                return false;
            }

            return true;
        },
        "error msg here");

and then attach it to attribute on view:

<input type="password" id="RegisterModel_Password" 
name="RegisterModel.Password" 
class="form-control" 
required="required" minlength="8" 
passwordCheck="passwordCheck"/>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 mu88
Solution 2 mathis1337