FluentValidation LogOnFailure override

c# asp.net-web-api fluentvalidation

457 просмотра

1 ответ

1711 Репутация автора

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.

Автор: Misiu Источник Размещён: 18.07.2016 12:23

Ответы (1)


0 плюса

4853 Репутация автора

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("");
        }
    }
}
Автор: Evgeny Levin Размещён: 04.08.2016 01:59
32x32