Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fluent Validation - validate string after trim

I'm having a play around with http://fluentvalidation.codeplex.com/ to validate some domain models.

I have a typical scenario where I want to validate a string, for example...

RuleFor(x => x.MyString).NotNull().NotEmpty().Length(2, 20).WithMessage("Please provide a string with a minium of 2 characters.");

...that all works just fine and dandy until I create a unit test that specifies that the MyString property must have a length of 2-20 characters not including whitespace.

So myObject.myString = "A" + new String(' ', 10); should fail validation.

I can get all this working with a .Must(IsValidString) and write all the logic myself in a...

    private bool IsValidString(string myString)
    {
       if(String.IsNullOrEmpty(myString))
           return false;

       // Then work on myString.Trim()'ed value. 
    }

...but then I loose all the lovely fluentvalidationness!!

Obviously I can make my unit test pass using this method, and all will be happy in my little the world, but am I missing a trick?

Many thanks.

like image 302
ETFairfax Avatar asked Feb 13 '14 14:02

ETFairfax


2 Answers

Use Transform to prepare the value prior to validation:

Version: 9.5+

Transform(i => i.InitialString, v => v?.Trim()).NotEmpty();

Versions: 9.0 - 9.4:

RuleFor(x => x.InitialString).Transform(v => v?.Trim()).NotEmpty();
like image 108
ademchenko Avatar answered Nov 14 '22 10:11

ademchenko


A little peek into the dll FluentValidation dll with http://ilspy.net/ and I was able to get inspiration to make the following TrimmedLengthValidator...

public static class DefaultValidatorExtensions
    {
        public static IRuleBuilderOptions<T, string> TrimmedLength<T>(this IRuleBuilder<T, string> ruleBuilder, int min, int max)
        {
            return ruleBuilder.SetValidator(new TrimmedLengthValidator(min, max));
        }
    }

public class TrimmedLengthValidator : PropertyValidator, ILengthValidator, IPropertyValidator
    {
        public int Min { get; private set; }
        public int Max { get; private set; }

        public TrimmedLengthValidator(int min, int max)
            : this(min, max, () => Messages.length_error)
        { }

        public TrimmedLengthValidator(int min, int max, Expression<Func<string>> errorMessageResourceSelector)
            : base(errorMessageResourceSelector)
        {
            this.Max = max;
            this.Min = min;

            if (max != -1 && max < min)
                throw new ArgumentOutOfRangeException("max", "Max should be larger than min.");
        }

        protected override bool IsValid(PropertyValidatorContext context)
        {
            if (context.PropertyValue == null)
                return true;

            int length = context.PropertyValue.ToString().Trim().Length;

            if (length < this.Min || (length > this.Max && this.Max != -1))
            {
                context.MessageFormatter.AppendArgument("MinLength", this.Min).AppendArgument("MaxLength", this.Max).AppendArgument("TotalLength", length);
                return false;
            }
            return true;
        }
    }

...which means I can simply change my validation to:

RuleFor(x => x.myString).NotEmpty().TrimmedLength(2, 20).WithMessage("Please provide a string with a minium of 2 characters.");

Rock on!

like image 26
ETFairfax Avatar answered Nov 14 '22 08:11

ETFairfax