FluentValidation LogOnFailure override

2019-07-15 08:24发布

Inside my validator class I have couple of rules.
I need to log to database some validation errors.

Here are my validators:

RuleFor(u => u.LastName)
    .Cascade(CascadeMode.StopOnFirstFailure)
    .NotEmpty().WithMessage("Last name is required")
    .Length(3, 20).WithMessage("Must have between 3 and 20 letters");

RuleFor(u => u.BirthDate)
    .Cascade(CascadeMode.StopOnFirstFailure)
    .NotNull().WithMessage("Birth date is required")
    .Must(c => c > new DateTime(1920, 01, 01)).WithMessage("Too old");

RuleFor(u => u.Age)
    .Cascade(CascadeMode.StopOnFirstFailure)
    .NotNull().WithMessage("This is required")
    .GreaterThan(18).WithMessage("Must be at least 18")
    .Must((model, age, context) =>
    {
        DateTime today = DateTime.Today;
        int ageToCompare = today.Year - model.BirthDate.Year;
        return ageToCompare == age;
    }).WithMessage("Invalid age");

For above rules I'd like to log only specific error messages. I'm aware that I can use OnAnyFailure like this:

RuleFor(u => u.Age)
    .Cascade(CascadeMode.StopOnFirstFailure)
    .NotNull().WithMessage("This is required")
    .GreaterThan(18).WithMessage("Must be at least 18").OnAnyFailure(LogOnFailure)
    .Must((model, age, context) =>
    {
        DateTime today = DateTime.Today;
        int ageToCompare = today.Year - model.BirthDate.Year;
        return ageToCompare == age;
    }).WithMessage("Invalid age").OnAnyFailure(LogOnFailure)

private void LogOnFailure(CreateAccountBindingModel obj))
    {
        Debug.WriteLine(obj);
    }

but this way I won't be able to log anything useful, because OnAnyFailure takes BindingModel as parameter, so I'll only get values user entered without error messages.

I've tried to create extension method that would work as OnAnyFailure but because I'm new to FluentValidation I wasn't able to even compile my code.

Below is my code:

public static class IRuleBuilderOptionsExtensions
{
    public static IRuleBuilderOptions<T, TProperty> OnAnyFailure<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, Action<T, PropertyValidatorContext> onFailure)
    {
        return rule;
        //return rule.Configure(config => {
        //  config.OnFailure = onFailure.CoerceToNonGeneric();
        //});
    }
}

This way I could be able to call:

private void LogOnFailure(CreateAccountBindingModel obj), PropertyValidatorContext context)
{
    //log logic
}

Basically what I need is to create override for LogOnFailure that will be able to access PropertyValidatorContext.

1条回答
Juvenile、少年°
2楼-- · 2019-07-15 08:33

You can validate your model second time to display it:

public class MyValidator
{
    public MyValidator()
    {
        // default behavior
        Validate();

        // works only when RuleSet specified explicitly as "Debug"
        RuleSet("Debug", ()=> {
            Validate();
            FailAndLogErrors();
        })

        private void Validate()
        {
            RuleFor(u => u.Age)
                //... set cascade mode, define rules with error messages
            RuleFor(u => u.LastName)
                //... set cascade mode, define rules with error messages
            RuleFor(u => u.BirthDate)
                //... set cascade mode, define rules with error messages
        }

        // force failing
        private void FailAndLogErrors()
        {
            RuleFor(m => m)
                .Must(m => false)
                .WithName("_fakeProperty_")
                .OnAnyFailure(m => LogIfFailed(this, m))
        }

        private void LogIfFailed(MyValidator validator, CreateAccountBindingModel obj))
        {
            var errors = validator.Validate(obj, "Debug").Errors;
            if (errors.Count > 1) // prevent displaying valid model
            {
                var fakeError = errors.First(e => e.PropertyName == "_fakeProperty_");
                errors.Remove(fakeError);
                WriteErrors(errors);
            }
        }

        private void WriteErrors(IList<ValidationFailure> errors)
        {
            foreach(var e in errors)
            {
                Debug.WriteLine(e);
            }
            Debug.WriteLine("");
        }
    }
}
查看更多
登录 后发表回答