如何在没有[Display]属性的情况下本地化模型字段名称?

时间:2019-07-11 16:38:57

标签: validation localization asp.net-core-mvc

我已经使用IValidationMetadataProvider方法为验证属性实现了本地化。

它在错误消息中使用模型的字段名称。

我想翻译资源字符串中的字段名,就像处理消息一样。但是我不想在每个字段上都放置[Display("FieldName")]属性。甚至不需要放置空的[Display]属性-这将是多余的样板代码。

理想情况下,我想以某种方式告诉MVC验证器,每当它需要字段名称作为验证消息时,它都应该询问一些自定义提供程序,以便我可以从IStringLocalizer实现中返回该值。

有什么方法可以将自定义字段名称提供给MVC验证程序,而不会随处吐出[Display]属性吗?

1 个答案:

答案 0 :(得分:0)

经过反复的尝试和寻找基于反射的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;
        }

    }