如何在ASP.NET Core中本地化验证属性的标准错误消息

时间:2019-12-11 10:45:55

标签: asp.net-core-2.2 validationattribute asp.net-core-localization

如何在ASP.NET Core(v2.2)中本地化验证属性的标准错误消息?例如, [Required] 属性具有以下错误消息“ xxx字段是必填字段。”; [EmailAddress] 具有“ xxx字段不是有效的电子邮件地址。”; [比较] 具有“ 'xxx'和'yyy'不匹配。”,依此类推。在我们的项目中,我们不使用英语,我想找到一种方法来翻译标准错误消息,而又不直接将它们写在每个数据模型类的每个属性中

3 个答案:

答案 0 :(得分:0)

这在docs中有详细说明。您可以执行以下任一操作:

  1. 在属性上使用ResourcePath选项。

    [Required(ResourcePath = "Resources")]
    

    然后,您将本地化的消息添加到Resources/Namespace.To.MyClass.[lang].resx

  2. 对所有类使用一个资源文件:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc()
            .AddDataAnnotationsLocalization(options => {
                options.DataAnnotationLocalizerProvider = (type, factory) =>
                    factory.Create(typeof(SharedResource));
            });
    }
    

答案 1 :(得分:0)

您已经知道可以在属性级别使用ErrorMessage来设置自定义错误消息,例如:

[Required(ErrorMessage = "MyRequiredErrorMessage")]

但这似乎不是您要寻找的解决方案。相反,如果可以在全局级别上设置 standard 验证属性的本地化,那就太好了。

我也一直在寻找这个,到这里结束,做了一些研究,最后弄清楚了如何做到这一点。这个想法很简单:找出属性的处理位置(适配器)并在全局级别覆盖ErrorMessage。

在适配器中仅设置ErrorMessage,因为这不是业务规则的地方。但是,有两个例外:

  1. 设置了本地自定义消息后,请勿替换该消息。

  2. StringLengthAttribute有两条消息。这取决于最小长度参数的值。

创建适配器:

using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.Extensions.Localization;
using System.ComponentModel.DataAnnotations;

public class ValidationAttributeErrorMessageProvider : ValidationAttributeAdapterProvider, IValidationAttributeAdapterProvider
{
    public ValidationAttributeErrorMessageProvider()
    {
    }

    IAttributeAdapter IValidationAttributeAdapterProvider.GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer)
    {
        // There are multiple error messages per attribute, but only one is used for validation.
        // No need to override the messages that are part of an exception.
        // These messages are meant for the developer, not the user.

        // Please note that DataTypeAttribute is ignored here. From referencesource:
        // This override always returns true. Subclasses should override this to provide the correct result.
        // https://referencesource.microsoft.com/#System.ComponentModel.DataAnnotations/DataAnnotations/DataTypeAttribute.cs,89
        // https://github.com/microsoft/referencesource/tree/master/System.ComponentModel.DataAnnotations/DataAnnotations/DataTypeAttribute.cs#L89

        // A local message (a custom message set on the attribute) should not be overwritten.
        if (!string.IsNullOrEmpty(attribute.ErrorMessage))
            return GetAttributeAdapter(attribute, stringLocalizer);

        // Though only default attributes are listed below, you can also include your custom attributes.
        if (attribute is CompareAttribute)
        {
            // CompareAttribute_MustMatch   '{0}' and '{1}' do not match.   
            // CompareAttribute_UnknownProperty Could not find a property named {0}.    

            attribute.ErrorMessage = "CompareAttribute_MustMatch";
        }
        else if (attribute is CreditCardAttribute)
        {
            // CreditCardAttribute_Invalid  The {0} field is not a valid credit card number.    

            attribute.ErrorMessage = "CreditCardAttribute_Invalid";
        }
        else if (attribute is CustomValidationAttribute)
        {
            // CustomValidationAttribute_Method_Must_Return_ValidationResult    The CustomValidationAttribute method '{0}' in type '{1}' must return System.ComponentModel.DataAnnotations.ValidationResult.  Use System.ComponentModel.DataAnnotations.ValidationResult.Success to represent success.  
            // CustomValidationAttribute_Method_Not_Found   The CustomValidationAttribute method '{0}' does not exist in type '{1}' or is not public and static.    
            // CustomValidationAttribute_Method_Required    The CustomValidationAttribute.Method was not specified. 
            // CustomValidationAttribute_Method_Signature   The CustomValidationAttribute method '{0}' in type '{1}' must match the expected signature: public static ValidationResult {0}(object value, ValidationContext context).  The value can be strongly typed.  The ValidationContext parameter is optional.    
            // CustomValidationAttribute_Type_Conversion_Failed Could not convert the value of type '{0}' to '{1}' as expected by method {2}.{3}.   
            // CustomValidationAttribute_Type_Must_Be_Public    The custom validation type '{0}' must be public.    
            // CustomValidationAttribute_ValidationError    {0} is not valid.   
            // CustomValidationAttribute_ValidatorType_Required The CustomValidationAttribute.ValidatorType was not specified.  

            attribute.ErrorMessage = "CustomValidationAttribute_ValidationError";
        }
        else if (attribute is EmailAddressAttribute)
        {
            // EmailAddressAttribute_Invalid    The {0} field is not a valid e-mail address.    

            attribute.ErrorMessage = "EmailAddressAttribute_Invalid";
        }
        else if (attribute is EnumDataTypeAttribute)
        {
            // EnumDataTypeAttribute_TypeCannotBeNull   The type provided for EnumDataTypeAttribute cannot be null. 
            // EnumDataTypeAttribute_TypeNeedsToBeAnEnum    The type '{0}' needs to represent an enumeration type.  

            attribute.ErrorMessage = "EnumDataTypeAttribute_TypeNeedsToBeAnEnum";
        }
        else if (attribute is FileExtensionsAttribute)
        {
            // FileExtensionsAttribute_Invalid  The {0} field only accepts files with the following extensions: {1} 

            attribute.ErrorMessage = "FileExtensionsAttribute_Invalid";
        }
        else if (attribute is MaxLengthAttribute)
        {
            // MaxLengthAttribute_InvalidMaxLength  MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length.   
            // MaxLengthAttribute_ValidationError   The field {0} must be a string or array type with a maximum length of '{1}'.    

            attribute.ErrorMessage = "MaxLengthAttribute_ValidationError";
        }
        else if (attribute is MinLengthAttribute)
        {
            // MinLengthAttribute_InvalidMinLength  MinLengthAttribute must have a Length value that is zero or greater.    
            // MinLengthAttribute_ValidationError   The field {0} must be a string or array type with a minimum length of '{1}'.    

            attribute.ErrorMessage = "MinLengthAttribute_ValidationError";
        }
        else if (attribute is PhoneAttribute)
        {
            // PhoneAttribute_Invalid   The {0} field is not a valid phone number.  

            attribute.ErrorMessage = "PhoneAttribute_Invalid";
        }
        else if (attribute is RangeAttribute)
        {
            // RangeAttribute_ArbitraryTypeNotIComparable   The type {0} must implement {1}.    
            // RangeAttribute_MinGreaterThanMax The maximum value '{0}' must be greater than or equal to the minimum value '{1}'.   
            // RangeAttribute_Must_Set_Min_And_Max  The minimum and maximum values must be set. 
            // RangeAttribute_Must_Set_Operand_Type The OperandType must be set when strings are used for minimum and maximum values.   
            // RangeAttribute_ValidationError   The field {0} must be between {1} and {2}.  

            attribute.ErrorMessage = "RangeAttribute_ValidationError";
        }
        else if (attribute is RegularExpressionAttribute)
        {
            // RegexAttribute_ValidationError   The field {0} must match the regular expression '{1}'.  
            // RegularExpressionAttribute_Empty_Pattern The pattern must be set to a valid regular expression.  

            attribute.ErrorMessage = "RegexAttribute_ValidationError";
        }
        else if (attribute is RequiredAttribute)
        {
            // RequiredAttribute_ValidationError    The {0} field is required.  

            attribute.ErrorMessage = "RequiredAttribute_ValidationError";
        }
        else if (attribute is StringLengthAttribute attr)
        {
            // StringLengthAttribute_InvalidMaxLength   The maximum length must be a nonnegative integer.   
            // StringLengthAttribute_ValidationError    The field {0} must be a string with a maximum length of {1}.    
            // StringLengthAttribute_ValidationErrorIncludingMinimum    The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.    

            // One exception to the rule, determine which message to show:
            if (attr.MinimumLength == 0)
                attribute.ErrorMessage = "StringLengthAttribute_ValidationError";
            else
                attribute.ErrorMessage = "StringLengthAttribute_ValidationErrorIncludingMinimum";
        }
        else if (attribute is UrlAttribute)
        {
            // UrlAttribute_Invalid The {0} field is not a valid fully-qualified http, https, or ftp URL.   

            attribute.ErrorMessage = "UrlAttribute_Invalid";
        }
        return GetAttributeAdapter(attribute, stringLocalizer);
    }
}

所有现有属性都通过此适配器。如您所见,仅ErrorMessage被更新。这样会将内部标志设置为自定义ErrorMessage(就像在属性本身上进行设置时一样)。

在Startup.ConfigureServices中注册适配器:

services.AddSingleton<IValidationAttributeAdapterProvider, ValidationAttributeErrorMessageProvider>();

有关此消息的一条评论。您可以使用 message 本身,例如"The {0} field is required."或验证错误的名称,例如"RequiredAttribute_ValidationError"

我之所以用这个名字( RequiredAttribute_ValidationError ),是因为我有一个用于多个类的资源字符串(例如 SharedResource )。因此,我可以简单地将行从DataAnnotations资源文件复制到资源文件(例如 SharedResource.fr.resx )。请注意,我必须在所有受支持的语言资源文件中复制条目,因为我不想显示名称

默认属性的错误消息可以在DataAnnotationsResources.resx中找到。

答案 2 :(得分:0)

如果您只想本地化错误消息而不是构建多语言站点,您可以尝试以下方法:(消息字符串可能是您的语言。)

  1. 添加自定义 IValidationMetadataProvider :
    public class MyModelMetadataProvider : IValidationMetadataProvider
    {
        public void CreateValidationMetadata(ValidationMetadataProviderContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException();
            }
            var validators = context.ValidationMetadata.ValidatorMetadata;

            // add [Required] for value-types (int/DateTime etc)
            // to set ErrorMessage before asp.net does it
            var theType = context.Key.ModelType;
            var underlyingType = Nullable.GetUnderlyingType(theType);

            if (theType.IsValueType &&
                underlyingType == null && // not nullable type
                validators.Where(m => m.GetType() == typeof(RequiredAttribute)).Count() == 0)
            {
                validators.Add(new RequiredAttribute());
            }
            foreach (var obj in validators)
            {
                if (!(obj is ValidationAttribute attribute))
                {
                    continue;
                }
                fillErrorMessage<RequiredAttribute>(attribute, 
                    "You must fill in '{0}'.");
                fillErrorMessage<MinLengthAttribute>(attribute, 
                    "Min length of '{0}' is {1}.");
                fillErrorMessage<MaxLengthAttribute>(attribute, 
                    "Max length of '{0}' is {1}.");
                fillErrorMessage<EmailAddressAttribute>(attribute, 
                    "Invalid email address.", true);
                // other attributes like RangeAttribute, CompareAttribute, etc
            }
        }
        private void fillErrorMessage<T>(object attribute, string errorMessage, 
            bool forceOverriding = false) 
            where T : ValidationAttribute
        {
            if (attribute is T validationAttribute)
            {
                if (forceOverriding ||
                    (validationAttribute.ErrorMessage == null 
                    && validationAttribute.ErrorMessageResourceName == null))
                {
                    validationAttribute.ErrorMessage = errorMessage;
                }
            }
        }
    }
  1. Startup.cs 中添加一些行:
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews()
                .AddMvcOptions(m => {
                    m.ModelMetadataDetailsProviders.Add(new MyModelMetadataProvider());

                    m.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(
                        fieldName => string.Format("'{0}' must be a valid number.", fieldName));
                    // you may check the document of `DefaultModelBindingMessageProvider`
                    // and add more if needed

                })
                ;
        }

the document of DefaultModelBindingMessageProvider

如果您能用日语阅读,请参阅this article了解更多详情。