How does FluentValidator determine what display name to use by default?

3k Views Asked by At

I'm working with FluentValidator in .NET Core. Everything is working beautifully for the most part, but I notice that when working with complex types, FluentValidator shows the full property name. For example, let's say I have a class named Address with Street, City, State, and Zip properties. Now let's say I have a form backed by a model property named Physical Address. If I make the street required, Fluent shows the following validation error:

'Physical Address. Street' must not be empty.

I prefer this to it just saying "Street" must not be empty because I might have multiple address fields on the page, so just displaying "Street" isn't specific enough. But I'd rather have it say:

'Physical Address Street' must not be empty. (with no period after the word address)

The example given by FluentValidation to globally override the display name is adding this in Startup.cs:

ValidatorOptions.DisplayNameResolver = (type, member, expression) => {
  if(member != null) {
     return member.Name + "Foo";
  }
  return null;
};

The override example works, but using this (minus the foo part) displays this validation error:

'Street' must not be empty. (the very thing I don't want because it's too generic)

What I need to know is what logic inside the lambda would produce the exact same result as the default behavior (i.e. Physical Address. Street, not just Street). Once I know that, I can fix removing the period with a simple defaultValue.Replace(".",""). Thanks!

1

There are 1 best solutions below

1
On BEST ANSWER

Update:

Much easier approach is to use ValidatorOptions.Global.PropertyNameResolver

ValidatorOptions.Global.DisplayNameResolver = (type, memberInfo, expression) =>
    ValidatorOptions.Global.PropertyNameResolver(type, memberInfo, expression).SplitPascalCase();

Original Answer:

Prepared complete demo based on github sources

Note: I am using ValidatorOptions.Global.DisplayNameResolver instead of ValidatorOptions.DisplayNameResolver since it is obsolete and will be removed in future versions

using FluentValidation;
using FluentValidation.Internal;
using System;
using System.Linq.Expressions;
using System.Reflection;

namespace ConsoleApp4
{
    public class Root
    {
        public PhysicalAddress PhysicalAddress { get; set; }
            = new PhysicalAddress();
    }

    public class PhysicalAddress
    {
        public string Street { get; set; }
    }

    public class RootValidator : AbstractValidator<Root>
    {
        public RootValidator()
        {
            RuleFor(x => x.PhysicalAddress.Street).NotNull();
        }
    }

    class Program
    {
        static string DefaultPropertyNameResolver(Type type, MemberInfo memberInfo, LambdaExpression expression)
        {
            if (expression != null)
            {
                var chain = PropertyChain.FromExpression(expression);
                if (chain.Count > 0) return chain.ToString();
            }

            return memberInfo?.Name;
        }

        static void Main(string[] args)
        {
            ValidatorOptions.Global.DisplayNameResolver = (type, memberInfo, expression) => 
                DefaultPropertyNameResolver(type, memberInfo, expression).SplitPascalCase();

            var res = new RootValidator().Validate(new Root());
        }
    }
}