You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何让自定义验证属性根据场景返回不同本地化错误消息?

我明白你的问题了——你想给自定义验证属性的不同错误场景配置本地化消息,但直接传资源值会因为不是常量报错,用nameof又只能拿到属性名而不是实际的本地化文本。这是因为C#的属性参数必须是常量表达式,所以没法直接把运行时的本地化字符串传进去,得换个思路,模仿.NET内置ValidationAttribute的本地化模式来实现。

解决方案:给每个错误场景添加资源类型+名称属性,在验证逻辑中反射获取本地化值

首先重构你的自定义验证属性,为每个错误类型添加对应的资源名称属性,同时可以加一个全局的资源类型(如果所有错误消息都用同一个资源文件的话),然后在IsValid方法里通过反射获取实际的本地化字符串:

public sealed class CustomAttribute : ValidationAttribute
{
    // 全局资源类型,所有错误消息默认使用这个资源文件
    public Type ErrorMessageResourceType { get; set; }

    // 每个错误场景对应的资源名称
    public string InvalidDateFormatResourceName { get; set; }
    public string InvalidDateFormatPastResourceName { get; set; }
    public string InvalidDateFormatFutureResourceName { get; set; }

    // 保留原有的直接设置消息的属性,作为本地化失败时的回退
    public string InvalidDateFormat { get; set; }
    public string InvalidDateFormatPast { get; set; }
    public string InvalidDateFormatFuture { get; set; }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        DateTime parsedDate;
        // 1. 验证日期格式
        if (!DateTime.TryParse(value?.ToString(), out parsedDate))
        {
            var errorMessage = GetLocalizedMessage(InvalidDateFormatResourceName) ?? InvalidDateFormat;
            return new ValidationResult(errorMessage);
        }

        // 2. 验证日期是否过旧(示例逻辑,根据你的需求调整)
        var minDate = DateTime.Now.AddYears(-100);
        if (parsedDate < minDate)
        {
            var errorMessage = GetLocalizedMessage(InvalidDateFormatPastResourceName) ?? InvalidDateFormatPast;
            return new ValidationResult(errorMessage);
        }

        // 3. 验证日期是否在未来(示例逻辑)
        if (parsedDate > DateTime.Now)
        {
            var errorMessage = GetLocalizedMessage(InvalidDateFormatFutureResourceName) ?? InvalidDateFormatFuture;
            return new ValidationResult(errorMessage);
        }

        return ValidationResult.Success;
    }

    // 通用方法:根据资源名称获取本地化字符串
    private string GetLocalizedMessage(string resourceName)
    {
        if (ErrorMessageResourceType == null || string.IsNullOrWhiteSpace(resourceName))
            return null;

        // 通过反射获取资源文件中的静态属性值
        var resourceProperty = ErrorMessageResourceType.GetProperty(
            resourceName,
            BindingFlags.Static | BindingFlags.Public);

        if (resourceProperty == null || resourceProperty.PropertyType != typeof(string))
            return null;

        return (string)resourceProperty.GetValue(null, null);
    }
}

使用方式

现在你可以像这样在模型上应用属性,用nameof指定资源名称,同时设置全局的资源类型:

[CustomAttribute(
    ErrorMessageResourceType = typeof(Language),
    InvalidDateFormatResourceName = nameof(Language.InvalidDateFormat),
    InvalidDateFormatPastResourceName = nameof(Language.InvalidDateFormatPast),
    InvalidDateFormatFutureResourceName = nameof(Language.InvalidDateFormatFuture)
)]
public string Date { get; set; }

关键说明

  1. 为什么原来的方法不行?
    C#要求属性的参数必须是常量表达式(比如字符串字面量、nameof结果、typeof表达式等),你不能直接传入Language.InvalidDateFormat——因为这是一个运行时才能确定的动态值,编译器不允许把它作为属性参数。而nameof(Language.InvalidDateFormat)只是返回属性的名称字符串(比如"InvalidDateFormat"),不是实际的本地化文本,所以你看到的就是这个字符串而不是正确的消息。

  2. 反射的作用?
    通过反射,我们可以在验证逻辑执行时(运行时),根据指定的资源类型和属性名称,动态获取资源文件中的本地化字符串,避开了属性参数必须是常量的限制。

  3. 注意事项

    • 确保你的资源文件(比如Language.resx)的访问修饰符设置为public,否则反射无法访问到里面的静态属性。
    • 如果不同错误消息需要用不同的资源文件,可以给每个错误场景单独添加ResourceType属性(比如InvalidDateFormatResourceType),在GetLocalizedMessage方法里使用对应的类型即可。
    • 如果需要错误消息支持格式化(比如包含具体的日期范围),可以在GetLocalizedMessage方法中添加参数,然后用string.Format拼接最终的消息。

内容的提问来源于stack exchange,提问作者user8114848

火山引擎 最新活动