40

I have a class containing one string property:

public class Bla
{
    public string Parameter { get; set; }
}

I would like to write a custom AbstractValidator, which checks that Parameter is equal to either one of these strings:

str1, str2, str3

I guess this would be a starting point:

RuleFor(x => x.Parameter).Must(x => x.Equals("str1") || x.Equals("str2") || x.Equals("str3")).WithMessage("Please only use: str1, str2, str3");

but can I chain this and also show an error message, ideally without hard-coding the possibilities, e.g.:

Please only use: str1, str2, str3
cs0815
  • 16,751
  • 45
  • 136
  • 299

4 Answers4

73

You may do this with a list containing your conditions

var conditions = new List<string>() { str1, str2, str3 };
RuleFor(x => x.Parameter)
  .Must(x => conditions.Contains(x))
  .WithMessage("Please only use: " + String.Join(",", conditions));
Thomas Ayoub
  • 29,063
  • 15
  • 95
  • 142
31

Usage:

RuleFor(m => m.Job)
    .In("Carpenter", "Welder", "Developer");

Output:

Job must be one of these values: Carpenter, Welder or Developer

Extension method:

public static class ValidatorExtensions
{
    public static IRuleBuilderOptions<T, TProperty> In<T, TProperty>(this IRuleBuilder<T, TProperty> ruleBuilder, params TProperty[] validOptions)
    {
        string formatted;
        if (validOptions == null || validOptions.Length == 0)
        {
            throw new ArgumentException("At least one valid option is expected", nameof(validOptions));
        }
        else if (validOptions.Length == 1)
        {
            formatted = validOptions[0].ToString();
        }
        else
        {
            // format like: option1, option2 or option3
            formatted = $"{string.Join(", ", validOptions.Select(vo => vo.ToString()).ToArray(), 0, validOptions.Length - 1)} or {validOptions.Last()}";
        }

        return ruleBuilder
            .Must(validOptions.Contains)
            .WithMessage($"{{PropertyName}} must be one of these values: {formatted}");
    }
}
Drew Delano
  • 1,421
  • 16
  • 21
  • This is great and we are using it, but can you think of a way to be able to send in a comparer as well? I've been playing around with it, and haven't been able to come up with a way. – Brian McCord Mar 27 '19 at 16:28
  • I think you could add a parameter to `In` (let's call it `cmprr`) for the comparer and then replace `.Must(validOptions.Contains)` with `.Must(i => validOptions.Any(o => cmprr.Equals(i, o)))` – Drew Delano Mar 28 '19 at 03:02
  • Let me know if that helps, or what you come up with. This might be useful too: https://fluentvalidation.net/custom-validators – Drew Delano Mar 28 '19 at 03:06
6

Agree with the code snippet from Thomas above. Another approach I like to take sometimes: if the validation makes sense as a domain concept, you can break it out into a method, such as the following:

RuleFor(x=>x.Parameter).Must(BeAValidParameter).WithMessage("Your parameter must be a valid parameter.");

    private static bool BeAValidParameter(string arg)
    {
        return arg.Equals("str1") || arg.Equals("str2") || arg.Equals("str3");
    }

I use this often for things like BeAValidZipCode, or BeAValidPhoneNumber, or some complex logic that expresses one business concept. You can use it in conjunction with the standard validation concepts (so, avoid trying to place all your validation in one method, for example).

SeanKilleen
  • 8,809
  • 17
  • 80
  • 133
6
RuleFor(x => x.Type).Must(x => x.Equals("K") || x.Equals("D")).WithMessage("Err Message!");
double-beep
  • 5,031
  • 17
  • 33
  • 41
ertugrulakdag
  • 67
  • 1
  • 3