Reputation:
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?
Upvotes: 15
Views: 6765
Reputation: 7618
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
.
Upvotes: 25
Reputation: 133
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;
}
}
Upvotes: 1
Reputation: 42374
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);
Upvotes: 4