12

I'm having an issue with FluentValidation where I want to display one message regardless of the validation error in a given chain. For example, I've defined a validation chain for one property below. I would expect that the chain would be evaluated and any failures would result in the message defined in the WithMessage() call below. However, it seems that it's short-circuiting and only displaying the FluentValidation default error message for the first error encountered. See code below:

RuleFor(s => s.ProposalDetail.AgeMin).NotNull()
        .GreaterThanOrEqualTo(1)
        .LessThanOrEqualTo(99)
        .WithMessage("Minimum Age entry is required and must range from 1 to 99 years.");

What's happening is that the AgeMin property is null, so the first NotNull() check is failing and the validation message reads "'Proposal Detail. Age Min' must not be empty." Proposal Detail is the name of the encapsulating view model. I've tried setting the CascadeMode for the entire validator to CascadeMode.Continue, but it has no effect.

How can I accomplish one message for one property validation chain?

  • `With the above code, the default validation message is returned, rather than the custom message at the end` where is the default message declared ..? can you post all relevant code.. also have you checked the [fluentvalidation for .NET Documentation /Examples](https://fluentvalidation.codeplex.com/wikipage?title=CreatingAValidator) – MethodMan Feb 19 '16 at 19:17
  • Yes, I have scoured the documentation and haven't found a similar example. I imagine I must use one rule using `Must()` to encapsulate all the rules to accomplish what I'm trying to do. I would have thought it to be a simple use case. The default FluentValidation message returns `'Proposal Detail. Age Min' must not be empty` because `NotNull()` is the first call in the chain. I have tried setting `CascadeMode = CascadeMode.Continue` for the validator, which had no effect. –  Feb 19 '16 at 19:24
  • Please provide a [mcve] that demonstrates your issue. It must be compilable code that we can run. – Enigmativity Feb 22 '16 at 00:48

3 Answers3

23

Update 4
I found a simpler solution that works with any version using the Configure method, so my original "Extension method" approach is not needed anymore

using FluentValidation;
using FluentValidation.Results;
using System;
using System.Linq;

namespace ConsoleApplication9
{
    class Program
    {
        static void Main(string[] args)
        {

            Customer customer = new Customer() { };
            CustomerValidator validator = new CustomerValidator();
            ValidationResult results = validator.Validate(customer);
            Console.WriteLine(results.Errors.First().ErrorMessage);
            Console.ReadLine();
        }
    }
    public class CustomerValidator : AbstractValidator<Customer>
    {

        public CustomerValidator()
        {
            RuleFor(s => s.Id).NotNull()
                .GreaterThanOrEqualTo(1)
                .LessThanOrEqualTo(99)
                .Configure(rule => rule.MessageBuilder = _ => "Minimum Age entry is required and must range from 1 to 99 years.");

        }

    }

    public class Customer { public int? Id { get; set; } }
}

Original answer: It works up to version 9 but it's more complex than the above
you can accomplish what you want with a simple extension method

using FluentValidation;
using FluentValidation.Internal;
using FluentValidation.Resources;
using FluentValidation.Results;
using System;
using System.Linq;

namespace ConsoleApplication9
{
    class Program
    {
        static void Main(string[] args)
        {

            Customer customer = new Customer() { };
            CustomerValidator validator = new CustomerValidator();
            ValidationResult results = validator.Validate(customer);
            Console.WriteLine(results.Errors.First().ErrorMessage);
            Console.ReadLine();
        }
    }
    public class CustomerValidator : AbstractValidator<Customer>
    {
        public CustomerValidator()
        {

            RuleFor(s => s.Id).NotNull()
                          .GreaterThanOrEqualTo(1)
                          .LessThanOrEqualTo(99)
                          .WithGlobalMessage("Minimum Age entry is required and must range from 1 to 99 years.");
        }

    }

    public class Customer { public int? Id { get; set; } }

    public static class MyExtentions
    {
        public static IRuleBuilderOptions<T, TProperty> WithGlobalMessage<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, string errorMessage)
        {
            foreach (var item in (rule as RuleBuilder<T, TProperty>).Rule.Validators)
                item.Options.ErrorMessageSource=new StaticStringSource(errorMessage);
        
            return rule;
        }
    }
}

The below works for any version but since it uses the Must method , it's not very clean and you miss the feel of a fluent interface.

using FluentValidation;
using FluentValidation.Results;
using System;
using System.Linq;

namespace ConsoleApplication9
{
    class Program
    {
        static void Main(string[] args)
        {
            Customer customer = new Customer() { };
            CustomerValidator validator = new CustomerValidator();
            ValidationResult results = validator.Validate(customer);
            Console.WriteLine(results.Errors.First().ErrorMessage);
            Console.ReadLine();
        }
    }
    public class CustomerValidator : AbstractValidator<Customer>
    {
        public CustomerValidator()
        {
            RuleFor(x => x)
                .Must(x => x.Id != null && x.Id >= 1 && x.Id <= 99)
                .WithMessage("Minimum Age entry is required and must range from 1 to 99 years.");
        }
    }
    public class Customer { public int? Id { get; set; } }
}

Update 3: (Apr 04/07/2019) In FluentValidation v8.2.2, The IRuleBuilderOptions interface do not have direct access to IRuleBuilderOptions.ErrorMessageSource property anymore, instead we should use: IRuleBuilderOptions.Options.ErrorMessageSource .

George Vovos
  • 7,563
  • 2
  • 22
  • 45
  • 1
    Your Update 2 is the right answer. I was going to look into a similar solution, but you beat me to it. – devuxer Feb 23 '16 at 19:04
  • @devuxer I only left the previous ones for educational purposes – George Vovos Feb 23 '16 at 21:32
  • 1
    @GeorgeVovos This is an excellent, elegant solution to this problem. I've confirmed that the solution is performing as expected. Thanks! –  Feb 28 '16 at 19:31
  • @GeorgeVovos In FluentValidation version 10, (and v9 I think!) we cannot access RuleBuilder anymore! (It has Internal access modifier [Check Link](https://github.com/FluentValidation/FluentValidation/blob/main/src/FluentValidation/Internal/RuleBuilder.cs#L29)) I get exception on this line: `foreach (var item in (rule as RuleBuilder).Rule.Validators)` . **How can I fix this problem in v10?** Thanks! – MRK Apr 09 '21 at 09:27
  • 1
    @MRK I'll check it later tonight or tomorrow – George Vovos Apr 09 '21 at 12:41
  • 1
    @MRK I found a better solution that works with all versions – George Vovos Apr 10 '21 at 13:20
  • @GeorgeVovos Thanks so much for your time and good stuff. Cheers – MRK Apr 10 '21 at 19:07
  • For Version 10 this hack works: `if (rule.GetType().GetProperty("Rule")?.GetValue(rule) is IValidationRuleConfigurable propertyRule) foreach (var component in propertyRule.Components.OfType>()) component.SetErrorMessage(errorMessage);` – Michael G. Sep 23 '21 at 13:25
4

The most straightforward solution would be to just set the message to a variable, and apply the same message after each rule:

var message = "Minimum Age entry is required and must range from 1 to 99 years.";
RuleFor(s => s.ProposalDetail.AgeMin)
    .NotNull()
        .WithMessage(message)
    .GreaterThanOrEqualTo(1)
        .WithMessage(message)
     .LessThanOrEqualTo(99)
        .WithMessage(message);
devuxer
  • 41,681
  • 47
  • 180
  • 292
  • 2
    I was actually using this solution until I saw George's edit. Both approaches are viable, but I like the cleanliness of George's solution. –  Feb 28 '16 at 19:29
  • This approach can be used when you need to assign different messages for different validation rules. – user1176058 Jul 24 '17 at 18:09
1

Replace the obsolete property assignment for ErrorMessageSource and use the bellow instead for FluentValidation versions >= 9.x

    public static class ValidatorExtensions
    {
        public static IRuleBuilderOptions<T, TProperty> WithGlobalMessage<T, TProperty>(this IRuleBuilderOptions<T, TProperty> rule, string errorMessage)
        {
            var rules = rule as RuleBuilder<T, TProperty>;
            foreach (var item in rules.Rule.Validators)
            {
                item.Options.SetErrorMessage(errorMessage);
            }

            return rule;
        }
    }
ageroh
  • 93
  • 1
  • 3