Fluent validation with dynamic message
Asked Answered
B

4

10

I am trying to building custom validation with dynamic message in fluent validation library.

For example :

public class CreateProcessValidator : AbstractValidator<CreateProcessVM>
{
    public CreateProcessValidator()
    {
        RuleFor(x => x.ProcessFile).Must((x,e) => IsProcessFileValid(x.ProcessFile))).WithMessage("Parse failed with error : {0}");        
    }

    public bool IsProcessFileValid(HttpPostedFileBase file)
    {
        var errorMessage = "..."  // pass result to validaton message ?
        // logic
        return false;
    }
}

Is here any workaround how to pass validation result ?

Thanks

Bellina answered 9/4, 2013 at 13:22 Comment(0)
H
20

Have you tried something like this?

public class IsProcessFileValid : PropertyValidator
{
    public IsProcessFileValid(): base("{ValidationMessage}") {}

    protected override IsValid(PropertyValidatorContext context)
    {
        if (!IsProcessFileValid1(context))
            context.MessageFormatter.AppendArgument("ValidationMessage",
                "Custom validation message #1");

        if (!IsProcessFileValid2(context))
            context.MessageFormatter.AppendArgument("ValidationMessage",
                "Custom validation message #2");

        // ...etc

        return true;
    }

    private bool IsProcessFileValid1(PropertyValidatorContext context)
    {
        // logic
        return false;
    }

    private bool IsProcessFileValid2(PropertyValidatorContext context)
    {
        // logic
        return false;
    }

    // ...etc
}

With extension method:

public static class IsProcessFileValidExtensions
{
    public static IRuleBuilderOptions<T, object> MustBeValidProcessFile<T>
        (this IRuleBuilder<T, object> ruleBuilder)
    {
        return ruleBuilder.SetValidator(new IsProcessFileValid());
    }

}

... and then use it without a custom WithMessage:

public CreateProcessValidator()
{
    RuleFor(x => x.ProcessFile).MustBeValidProcessFile();        
}

By creating a custom PropertyValidator, you can encapsulate the default validation message within that class and make it dynamic. However you must not use the .WithMessage extension when declaring the RuleFor, because that would override the default validation message which you customized directly inside the PropertyValidator.

Homophile answered 9/4, 2013 at 14:22 Comment(3)
Thanks for answer, i am little busy now, but next week i will try.Bellina
Did you get a chance to try this? I have a feeling it will work.Homophile
Thanks - exactly what I needed. Particularly useful for me in validating a HttpPostedFileBase of type ZipFile (DotNetZip)Nolen
F
3

Faced the same issue, while trying to insert exception message into WithMessage(). It worked with the method overload taking Func<T, string> messageProvider as parameter.

Here is the solution presented on the posters example (working code, FluentValidation v 9.1):

public class CreateProcessVM
{
    public object ProcessFile { get; set; }
}

public class CreateProcessValidator : AbstractValidator<CreateProcessVM>
{
    public CreateProcessValidator()
    {
        var message = "Something went wrong.";
        RuleFor(x => x.ProcessFile)
            .Must((x, e) => IsProcessFileValid(x.ProcessFile, out message))
            // .WithMessage(message); will NOT work
            .WithMessage(x => message); //Func<CreateProcessVM, string> as parameter
    }

    public bool IsProcessFileValid(object file, out string errorMessage)
    {
        errorMessage = string.Empty;
        try
        {
            Validate(file);
            return true;
        }
        catch (InvalidOperationException e)
        {
            errorMessage = e.Message;
            return false;
        }
    }

    private void Validate(object file)
    {
        throw new InvalidOperationException("File of type .custom is not allowed.");
    }
}

And a test demonstrating that we really get the exception message in the error message:

[Fact]
public void Test()
{
    var validator = new CreateProcessValidator();
    var result = validator.Validate(new CreateProcessVM());
    Assert.False(result.IsValid);
    Assert.Equal("File of type .custom is not allowed.", result.Errors[0].ErrorMessage);
}
Formica answered 18/8, 2020 at 8:30 Comment(0)
C
1

Here is how I solved it. Tested with FluentValidation v8.5.0

class EmptyValidationMessage : IStringSource
{
    public string ResourceName => null;

    public Type ResourceType => null;

    public string GetString(IValidationContext context)
    {
        return string.Empty;
    }

    public static readonly EmptyValidationMessage Instance = new EmptyValidationMessage();
}

public class MyPropValidator : PropertyValidator
{
    public MyPropValidator() : base(EmptyValidationMessage.Instance)
    {
    }

    protected override bool IsValid(PropertyValidatorContext context)
    {
        // if not valid

        Options.ErrorMessageSource = new StaticStringSource("my message");

        // you can do LanguageStringSource, LazyStringSource, LocalizedStringSource, etc

        // example with localized string (https://github.com/clearwaterstream/LocalizedString.FluentValidation)

        Options.ErrorMessageSource = new LocalizedStringSource("my message").InFrench("moi message");

        return false;
    }
}
Cirrocumulus answered 17/3, 2020 at 20:42 Comment(0)
M
0

There's no way to do that. I would split the complex validation method you currently have into smaller methods (IsProcessFileValid1, IsProcessFileValid2, IsProcessFileValid3, ...) so that you could have more fine grained control over the error message. Also each method will be responsible for validating only once concern making them more reusable (single responsibility):

RuleFor(x => x.ProcessFile)
    .Must(IsProcessFileValid1)
    .WithMessage("Message 1")
    .Must(IsProcessFileValid2)
    .WithMessage("Message 2")
    .Must(IsProcessFileValid3)
    .WithMessage("Message 3");

Also notice how I simplified the lambda as the method could directly be passed to Must as argument.

Maureenmaureene answered 9/4, 2013 at 13:31 Comment(1)
Yes, split methods was first idea, but this isnt solve my problem because in one of small method, i am trying validate xml file against xsd and need pass result to user...Bellina

© 2022 - 2024 — McMap. All rights reserved.