【问题标题】:How to localize model field names without [Display] attribute?如何本地化没有 [Display] 属性的模型字段名称?
【发布时间】:2019-11-21 10:40:18
【问题描述】:
我已经使用 IValidationMetadataProvider 方法实现了验证属性的本地化。
它在错误消息中使用模型的字段名称。
我想从资源字符串中翻译字段名称,就像我对消息所做的那样。但我不想在每个字段上都加上[Display("FieldName")] 属性。甚至不需要放置空的 [Display] 属性 - 这将是多余的样板代码。
理想情况下,我想以某种方式告诉 MVC 验证器,每次它需要验证消息的字段名称时,它应该询问一些自定义提供程序,以便我可以从 IStringLocalizer 实现返回值。
有什么方法可以将自定义字段名称输入 MVC 验证器,而不会到处乱扔[Display] 属性?
【问题讨论】:
标签:
validation
localization
asp.net-core-mvc
【解决方案1】:
经过大量的试验和错误以及寻找基于反射的 DisplayAttribute 注入后,我发现可以注入我自己的 IDisplayMetadataProvider。
所以,现在我可以完全省略 [Display] 属性,我的类将自动从资源中提取字段名称,匹配模型属性名称。额外的好处 - 它将确保匹配属性的名称一致,因为它将使用全局默认值,除非显式 [Display(Name = "SomeOtherResourceKey")] 应用于特定属性。
public class LocalizableInjectingDisplayNameProvider : IDisplayMetadataProvider
{
private IStringLocalizer _stringLocalizer;
private Type _injectableType;
public LocalizableInjectingDisplayNameProvider(IStringLocalizer stringLocalizer, Type injectableType)
{
_stringLocalizer = stringLocalizer;
_injectableType = injectableType;
}
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
// ignore non-properties and types that do not match some model base type
if (context.Key.ContainerType == null ||
!_injectableType.IsAssignableFrom(context.Key.ContainerType))
return;
// In the code below I assume that expected use of field name will be:
// 1 - [Display] or Name not set when it is ok to fill with the default translation from the resource file
// 2 - [Display(Name = x)]set to a specific key in the resources file to override my defaults
var propertyName = context.Key.Name;
var modelName = context.Key.ContainerType.Name;
// sanity check
if (string.IsNullOrEmpty(propertyName) || string.IsNullOrEmpty(modelName))
return;
var fallbackName = propertyName + "_FieldName";
// If explicit name is missing, will try to fall back to generic widely known field name,
// which should exist in resources (such as "Name_FieldName", "Id_FieldName", "Version_FieldName", "DateCreated_FieldName" ...)
var name = fallbackName;
// If Display attribute was given, use the last of it
// to extract the name to use as resource key
foreach (var attribute in context.PropertyAttributes)
{
var tAttr = attribute as DisplayAttribute;
if (tAttr != null)
{
// Treat Display.Name as resource name, if it's set,
// otherwise assume default.
name = tAttr.Name ?? fallbackName;
}
}
// At first, attempt to retrieve model specific text
var localized = _stringLocalizer[name];
// Final attempt - default name from property alone
if (localized.ResourceNotFound)
localized = _stringLocalizer[fallbackName];
// If not found yet, then give up, leave initially determined name as it is
var text = localized.ResourceNotFound ? name : localized;
context.DisplayMetadata.DisplayName = () => text;
}
}