FluentValidation.NET 等同于 [Display(Name)]

FluentValidation.NET equivalent to [Display(Name)]

FluentValidation.NET 之前,我可以像这样正确地给自定义标签:

[Display(Name="Blah")]
public string BlahBlahBlah { get; set; }

我可以通过多种方式使用它:

@Html.LabelFor(m => m.BlahBlahBlah)
@Html.DisplayNameFor(m => m.BlahBlahBlah)

<label asp-for="BlahBlahBlah"></label>

现在我想从我的模型中删除所有数据注释,并转向流畅的验证。在我的验证器中,我有这个:

RuleFor(o => o.BlahBlahBlah)
    .NotEmpty()
    .WithName("Blah");

但这不起作用。为什么?

FluentValidation 中的

WithName 方法仅用于调整验证错误消息,如果您想将 C# 属性 名称替换为更用户友好的名称(请参阅 覆盖默认 属性 名称 以获取详细信息)。

所以答案是 - 通常不能将 Display 替换为 WithName(),仅用于验证错误消息。

如果有帮助,我将一些属性和助手组合在一起,使其更加动态。

WithDisplayNameAttribute.cs

[AttributeUsage(AttributeTargets.Property)]
public class WithDisplayNameAttribute : Attribute
{
    public WithDisplayNameAttribute(string displayName)
    {
        DisplayName = displayName;
    }

    /// <summary>
    /// The preferred friendly name to display for this property during validation.
    /// </summary>
    public string DisplayName { get; set; }
}

WithDisplayNameHelper.cs

internal static class WithDisplayNameHelper
{
    public static IReadOnlyDictionary<string, string> Map { get; }

    static WithDisplayNameHelper()
    {
        var core = typeof(WithDisplayNameHelper).Assembly;
        var map = new Dictionary<string, string>();
        foreach (var parentModelType in core.GetExportedTypes().Where(x => x.IsClass))
        {
            foreach (var prop in parentModelType.GetProperties())
            {
                var att = prop.GetCustomAttribute<WithDisplayNameAttribute>();
                if (att == null) continue;
                var key = GetKey(parentModelType, prop);
                if (!map.ContainsKey(key))
                {
                    map.Add(key, att.DisplayName);
                }
            }
        }

        Map = new ReadOnlyDictionary<string, string>(map);
    }

    /// <summary>
    /// Gets the key to use for this property.
    /// </summary>
    /// <param name="parent">The parent class containing the property.</param>
    /// <param name="prop">The property described by the display name.</param>
    /// <returns></returns>
    private static string GetKey(Type parent, PropertyInfo prop) => GetKey(parent, prop.Name);
    
    /// <inheritdoc cref="GetKey(System.Type,System.Reflection.PropertyInfo)"/>
    private static string GetKey(Type parent, string prop) => $"{parent.FullName}.{prop}";

    /// <summary>
    /// Retrieves the display name if one was set using the <see cref="WithDisplayNameAttribute"/>. Otherwise will return the given <paramref name="propertyName"/>.
    /// </summary>
    /// <param name="parent">The parent class containing the property.</param>
    /// <param name="propertyName">The property name.</param>
    /// <returns></returns>
    public static string GetDisplayNameOrDefault(Type parent, string propertyName) =>
        Map.TryGetValue(GetKey(parent, propertyName), out var value)
        ? value
        : propertyName;

    /// <summary>
    /// Attempts to retrieve the display name if one was set using the <see cref="WithDisplayNameAttribute"/>.
    /// </summary>
    /// <inheritdoc cref="GetDisplayNameOrDefault"/>
    /// <returns></returns>
    public static bool TryGetDisplayName(Type parent, string propertyName, out string result) =>
        Map.TryGetValue(GetKey(parent, propertyName), out result);


}

以及扩展方法:

    /// <summary>
    /// Specifies a custom property name to use within the error message.
    /// </summary>
    /// <param name="rule">The current rule</param>
    /// <returns></returns>
    public static IRuleBuilderOptions<T, TProperty> WithDisplayName<T, TProperty>(
        this IRuleBuilderOptions<T, TProperty> rule)
    {
        
        return rule.Configure(x =>
        {
            if (WithDisplayNameHelper.TryGetDisplayName(typeof(T), x.PropertyName, out var displayName))
                x.DisplayName = new StaticStringSource(displayName);
        });
    }