Fluent Validation - validate string after trim
Asked Answered
O

5

8

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.

Oneway answered 13/2, 2014 at 14:17 Comment(0)
O
5

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!

Oneway answered 13/2, 2014 at 15:53 Comment(1)
You can further reduce the validation code by removing the .NotNull() call as the .NotEmpty() call after handles null as well.Bravery
D
6

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();
Douglassdougy answered 1/7, 2021 at 10:10 Comment(1)
This is deprecated and no longer available in version 12 and up. github.com/FluentValidation/FluentValidation/issues/2072Hailee
O
5

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!

Oneway answered 13/2, 2014 at 15:53 Comment(1)
You can further reduce the validation code by removing the .NotNull() call as the .NotEmpty() call after handles null as well.Bravery
H
1

In newer versions this is simply solved by using the Must function.

If you only use the function once you can just use the Must function like this:

RuleFor(c => c.Name).Must(c => c.Trim().Length > 5);

For use in all validators use an extension like this:

public static class DefaultValidationRulesExtension
{
        public static IRuleBuilderOptions<T, string> TrimmedMinLength<T>(this IRuleBuilder<T, string> ruleBuilder, int min)
        {
            return ruleBuilder.Must(s => s.Trim().Length > min);
        }
}
Hailee answered 18/4, 2023 at 11:26 Comment(0)
F
0

I'm not really familiar with this framework, but would something like this work? You'll still keep most of your fluency, but checking for whitespace requires you to either write your custom validator (which is a big hassle), or by using the Predicate validator

RuleFor(x => x.MyString)
    .NotNull()
    .NotEmpty()
    .Length(2, 20)
        .WithMessage("Please provide a string with a minium of 2 characters.")
    .Must(myString => myString == Regex.Replace( myString, @"s", "" ))
Filose answered 13/2, 2014 at 14:33 Comment(1)
this will not work, this is attempting making sure the length is exactly the same after trim, but instead its replacing all of the s with empty chars instead of space characters. But in both senarios this answer is wrong. It should be validating the length after trimCreepy
S
-1
RuleFor(x => x.myStringBefore).Transform(c => c.Trim()).NotEmpty();

Use Transform

You can use trim or substring... (after transforming) fluent validation allows to apply any valid rules to the transformed value.

Schadenfreude answered 5/6, 2020 at 11:28 Comment(1)
That'll throw null reference exception from fluent validation framework when myStringBefore is null.Enschede

© 2022 - 2025 — McMap. All rights reserved.