FluentValidator 如何确定默认使用的显示名称?

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

我正在使用 .NET Core 中的 FluentValidator。大多数情况下一切都运行良好,但我注意到在处理复杂类型时,FluentValidator 会显示完整的 属性 名称。例如,假设我有一个名为 Address 的 class,其中包含 Street、City、State 和 Zip 属性。现在假设我有一个由名为 Physical Address 的模型 属性 支持的表单。如果我要求街道,Fluent 会显示以下验证错误:

'Physical Address. Street' must not be empty.

我更喜欢这个而不是它只是说“街道”不能为空,因为我可能在页面上有多个地址字段,所以只显示“街道”不够具体。但我宁愿让它说:

'Physical Address Street' must not be empty.(字地址后无句号)

FluentValidation 给出的全局覆盖显示名称的示例在 Startup.cs 中添加:

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

覆盖示例有效,但使用它(减去 foo 部分)显示此验证错误:

'Street' must not be empty.(我不想要的东西,因为它太笼统了)

我需要知道的是 lambda 内部的什么逻辑会产生与默认行为完全相同的结果(即 Physical Address.Street,而不仅仅是 街道)。一旦我知道了这一点,我就可以用一个简单的 defaultValue.Replace(".","") 来解决删除句点的问题。谢谢!

更新:

更简单的方法是使用 ValidatorOptions.Global.PropertyNameResolver

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

原答案:

根据github sources

准备了完整的演示

注意:我使用的是 ValidatorOptions.Global.DisplayNameResolver 而不是 ValidatorOptions.DisplayNameResolver,因为它已过时,将在未来版本中删除

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());
        }
    }
}