【问题标题】:How to provide localized validation messages for validation attributes如何为验证属性提供本地化验证消息
【发布时间】:2017-04-08 20:34:37
【问题描述】:

我正在开发一个ASP.NET Core 应用程序,我想覆盖数据注释的默认验证错误消息,例如RequiredMinLengthMaxLength 等。我阅读了 Globalization and localization in ASP.NET Core,而且好像没有涵盖我要找的东西……

例如,Required 属性的验证错误消息对于任何模型属性总是相同的。默认文本只是声明:{0} 字段是必需的,其中{0} 占位符将填充属性的显示名称。

在我的视图模型中,我使用不带任何命名参数的 Required 属性,就像这样...

class ViewModel
{
    [Required, MinLength(10)]
    public string RequiredProperty { get; set; }
}

在我看来,设置ErrorMessageErrorMessageResourceName(和ErrorMessageResourceType)是不必要的开销。我想我可以实现类似于IDisplayMetadataProvider 的东西,允许我返回应用属性的错误消息,以防验证失败。这可能吗?

【问题讨论】:

  • 当然可以 - 只需编写您自己的属性即可。您将能够生成任何您想要的消息...
  • 您是否尝试将线程文化设置为您喜欢的语言?
  • @Tseng 是的,但我也想更改措辞...
  • @DawidRutkowski 我想自定义转换注释的功能,而不是发明自定义注释。
  • 我认为您可以创建一个中间件来处理错误并更改其中的错误消息。不过不确定。

标签: c# asp.net-core asp.net-core-mvc


【解决方案1】:

对于那些最终在这里寻找通用解决方案的人来说,解决它的最佳方法是使用验证元数据提供程序。我的解决方案基于这篇文章: AspNetCore MVC Error Message,我使用了.net框架风格的本地化,并简化为使用设计的提供者。

  1. 在您的项目中添加一个资源文件,例如 ValidationsMessages.resx,并将访问修饰符设置为 Internal 或 Public,以便生成后面的代码,这将为您提供 ResourceManager 静态实例。
  2. 为每种语言添加自定义本地化ValidationsMessages.es.resx。切记不要为此文件设置访问修饰符,代码是在第 1 步创建的。
  3. 添加 IValidationMetadataProvider 的实现
  4. 根据属性类型名称添加本地化,​​例如“RequiredAtrribute”。
  5. 在启动文件中设置您的应用。

验证消息示例。es.resx

IValidatioMetadaProvider 示例:

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using System.ComponentModel.DataAnnotations;
using System.Reflection;

public class LocalizedValidationMetadataProvider : IValidationMetadataProvider
{
    public LocalizedValidationMetadataProvider()
    {
    }

    public void CreateValidationMetadata(ValidationMetadataProviderContext context)
    {
        if (context.Key.ModelType.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(context.Key.ModelType.GetTypeInfo()) == null && context.ValidationMetadata.ValidatorMetadata.Where(m => m.GetType() == typeof(RequiredAttribute)).Count() == 0)
            context.ValidationMetadata.ValidatorMetadata.Add(new RequiredAttribute());
        foreach (var attribute in context.ValidationMetadata.ValidatorMetadata)
        {
            var tAttr = attribute as ValidationAttribute;
            if (tAttr?.ErrorMessage == null && tAttr?.ErrorMessageResourceName == null)
            {
                var name = tAttr.GetType().Name;
                if (Resources.ValidationsMessages.ResourceManager.GetString(name) != null)
                {
                    tAttr.ErrorMessageResourceType = typeof(Resources.ValidationsMessages);
                    tAttr.ErrorMessageResourceName = name;
                    tAttr.ErrorMessage = null;
                }
            }
        }
    }
}

将提供程序添加到 Startup 类的 ConfigureServices 方法中:

services.AddMvc(options =>
{
     options.ModelMetadataDetailsProviders.Add(new LocalizedValidationMetadataProvider());
})

【讨论】:

  • 我想指出,上面的RequiredAttribute 检查还将属性添加到可为空的值类型,通常不应该这样做。您可以通过在 if 子句中添加以下内容来避免这种情况: && Nullable.GetUnderlyingType(context.Key.ModelType.GetTypeInfo()) == null
  • 条件if (tAttr?.ErrorMessage == null && tAttr?.ErrorMessageResourceName == null)是错误的,如果tAttr为空,则为真。你可以使用if (attribute is ValidationAttribute tAttr && tAttr.ErrorMessage == null && tAttr.ErrorMessageResourceName == null)
  • 那你也可以删除tAttr.ErrorMessage = null;
【解决方案2】:

如果要更改全文,则应使用资源文件对其进行本地化。

每个ValidationAttribute 都有ErrorMessageResourceTypeErrorMessageResourceName 的属性(参见源代码here)。

[Required(ErrorMessageResourceName = "BoxLengthRequired", ErrorMessageResourceType = typeof(SharedResource))]

更新

好的,似乎有一种方法可以使用本地化提供程序对其进行本地化,但它仍然有点笨拙,并且至少需要属性上的一个属性(来自this blog post - 警告词,它最初是为了旧的 RC1 或 RC2 版本。它应该可以工作,但该文章中的某些 API 可能无法工作):

在启动中:

services.AddMvc()
   .AddViewLocalization()
   .AddDataAnnotationsLocalization();

在您的模型上:

[Required(ErrorMessage = "ViewModelPropertyRequired"), MinLength(10, ErrorMessage = "ViewModelMinLength")]
public string RequiredProperty { get; set; }

并实施/使用使用 DB 的本地化提供程序(即https://github.com/damienbod/AspNet5Localization)。

【讨论】:

  • 好的,但是如果我不想使用资源文件而是数据库怎么办?除此之外,我想避免额外的争论。
  • 查看更新,但仍至少需要ErrorMessage,并且您在此过程中丢失了参数名称。 也许如果您离开{0} 或搜索它,它会起作用,但我不会打赌它或数据库中的本地化消息。避免额外参数的唯一方法是使用来自 github.com/dotnet/corefx/blob/v1.1.0/src/… 的原始字符串,因为它似乎还没有本地化,但它可能会在未来发生变化,你必须解析所有 resx 文件来填充你的数据库每种语言的键
  • 话虽如此,您可以尝试在配置数据库本地化提供程序时使用RequiredAttribute_ValidationError 作为键并返回您想要的字符串
  • 如果属性是 NotMapped,我认为这不起作用。它似乎重新调整了它的要求,但不显示自定义错误消息。 aspnetcore 2.0
  • @Mike:这有什么关系? NotMappedAttribute 不是验证属性(它继承自 Attribute,而不是 ValidationAttribute
【解决方案3】:

我遇到了同样的问题,我使用的解决方案是创建验证属性的子类以提供本地化的错误消息。

为了防止程序员意外使用非本地化版本,我只是省略了非本地化库的 using 语句。

【讨论】:

    【解决方案4】:

    所以,我来到这里是因为创建了自己的自定义 IStringLocalizer,并想分享我的解决方案,因为 @jlchavez 帮助了我。

    我创建了一个 MongoDB IStringLocalizer,并希望通过 DataAnnotations 使用这些资源。问题是 DataAnnotations 属性期望通过暴露资源的静态类进行本地化。

    jlchavez's answer 的一项改进是这将修复所有 ValidationAttribute(s) 的资源消息

    services.AddTransient<IValidationMetadataProvider, Models.LocalizedValidationMetadataProvider>();
    services.AddOptions<MvcOptions>()
        .Configure<IValidationMetadataProvider>((options, provider) =>
        {
            options.ModelMetadataDetailsProviders.Add(provider);
        });
    
    
    public class Resource
    {
        public string Id => Culture + "." + Name;
        public string Culture { get; set; }
        public string Name { get; set; }
        public string Text { get; set; }
    }
    
    public class MongoLocalizerFactory : IStringLocalizerFactory
    {
        private readonly IMongoCollection<Resource> _resources;
    
        public MongoLocalizerFactory(IMongoCollection<Resource> resources)
        {
            _resources = resources;
        }
    
        public IStringLocalizer Create(Type resourceSource)
        {
            return new MongoLocalizer(_resources);
        }
    
        public IStringLocalizer Create(string baseName, string location)
        {
            return new MongoLocalizer(_resources);
        }
    }
    
    public class MongoLocalizer : IStringLocalizer
    {
        private readonly IMongoCollection<Resource> _resources;
    
        public MongoLocalizer(IMongoCollection<Resource> resources)
        {
            _resources = resources;
        }
    
        public LocalizedString this[string name]
        {
            get
            {
                var value = GetString(name);
                return new LocalizedString(name, value ?? name, resourceNotFound: value == null);
            }
        }
    
        public LocalizedString this[string name, params object[] arguments]
        {
            get
            {
                var format = GetString(name);
                var value = string.Format(format ?? name, arguments);
                return new LocalizedString(name, value, resourceNotFound: format == null);
            }
        }
    
        public IStringLocalizer WithCulture(CultureInfo culture)
        {
            CultureInfo.DefaultThreadCurrentCulture = culture;
    
            return new MongoLocalizer(_resources);
        }
    
        public IEnumerable<LocalizedString> GetAllStrings(bool includeAncestorCultures)
        {
            var resources = _resources.Find(r => r.Culture == CultureInfo.CurrentCulture.Parent.Name).ToList();
            return resources.Select(r => new LocalizedString(r.Name, r.Text, false));
        }
    
        private string GetString(string name)
        {
            var resource = _resources.Find(r => r.Culture == CultureInfo.CurrentCulture.Parent.Name && r.Name == name).SingleOrDefault();
            if (resource != null)
            {
                return new LocalizedString(resource.Name, resource.Text, false);
            }
            return new LocalizedString(name, name, true);
        }
    }
    
    public class LocalizedValidationMetadataProvider : IValidationMetadataProvider
    {
        private IStringLocalizer _localizer;
    
        public LocalizedValidationMetadataProvider(IStringLocalizer localizer)
        {
            _localizer = localizer;
        }
    
        public void CreateValidationMetadata(ValidationMetadataProviderContext context)
        {
            foreach(var metadata in context.ValidationMetadata.ValidatorMetadata)
            {
                if (metadata is ValidationAttribute attribute)
                {
                    attribute.ErrorMessage = _localizer[attribute.ErrorMessage].Value;
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-25
      • 1970-01-01
      • 2011-08-26
      • 1970-01-01
      • 2019-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多