本地化ASP.NET core模型绑定错误消息
默认错误消息:
MissingBindRequiredValueAccessor A value for the '{0}' property was not provided.
MissingKeyOrValueAccessor A value is required.
ValueMustNotBeNullAccessor The value '{0}' is invalid.
AttemptedValueIsInvalidAccessor The value '{0}' is not valid for {1}.
UnknownValueIsInvalidAccessor The supplied value is invalid for {0}.
ValueIsInvalidAccessor The value '{0}' is invalid.
ValueMustBeANumberAccessor The field {0} must be a number.
MissingRequestBodyRequiredValueAccessor A non-empty request body is required.
NonPropertyAttemptedValueIsInvalidAccessor The value '{0}' is not valid.
NonPropertyUnknownValueIsInvalidAccessor The supplied value is invalid.
NonPropertyValueMustBeANumberAccessor The field must be a number.
若要本地化ASP.NET Core 模型绑定错误消息,请按照下列步骤操作:
-
创建资源文件 - 在解决方案的Resources文件夹下创建资源文件,并将文件命名为ModelBindingMessages.resx。名称可以是其他任何名称,但我们将使用它来创建本地化程序。
-
添加资源键 - 打开资源文件并添加要用于本地化错误消息的键和值。我使用了键和值
-
配置选项 - 在
ConfigureServices
方法中,添加时Mvc
,配置其选项以设置以下内容的消息访问者ModelBindingMessageProvider
-
1 services.AddMvc(options => 2 { 3 IStringLocalizerFactory F = services.BuildServiceProvider(). 4 GetService<IStringLocalizerFactory>(); 5 IStringLocalizer L = F.Create("ModelBindingMessages", 6 "AspNetCoreLocalizationSample"); 7 options.ModelBindingMessageProvider. 8 SetValueIsInvalidAccessor((x) => L["The value '{0}' is invalid."]); 9 options.ModelBindingMessageProvider.SetValueMustBeANumberAccessor( 10 (x) =>L["The field {0} must be a number."]); 11 options.ModelBindingMessageProvider.SetMissingBindRequiredValueAccessor( 12 (x) => L["A value for the '{0}' property was not provided.", x]); 13 options.ModelBindingMessageProvider.SetAttemptedValueIsInvalidAccessor( 14 (x, y) => L["The value '{0}' is not valid for {1}.", x, y]); 15 options.ModelBindingMessageProvider.SetMissingKeyOrValueAccessor( 16 () => L["A value is required."]); 17 options.ModelBindingMessageProvider.SetUnknownValueIsInvalidAccessor( 18 (x) => L["The supplied value is invalid for {0}.", x]); 19 options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor( 20 (x) => L["Null value is invalid.", x]); 21 }).AddDataAnnotationsLocalization() 22 .AddViewLocalization(); 23 services.Configure<RequestLocalizationOptions>(options => 24 { 25 var supportedCultures = new[]{new CultureInfo("en"), new CultureInfo("zh-cn")}; 26 options.DefaultRequestCulture = new RequestCulture("en", "en"); 27 options.SupportedCultures = supportedCultures; 28 options.SupportedUICultures = supportedCultures; 29 });
-
还要在
Configure
方法开头添加此代码:1 var supportedCultures = new[] { new CultureInfo("en"), new CultureInfo("zh-CN") }; 2 app.UseRequestLocalization(new RequestLocalizationOptions() 3 { 4 DefaultRequestCulture = new RequestCulture(new CultureInfo("en")), 5 SupportedCultures = supportedCultures, 6 SupportedUICultures = supportedCultures 7 });
-
通过查看源码 在 Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.DataAnnotationsMetadataProvider中有IDisplayMetadataProvider的实现
主要是判断DisplayName不为空和ResourceType为空的时候使用IStringLocalizerFactory
代码片段如下:
1 var dataTypeAttribute = attributes.OfType<DataTypeAttribute>(). 2 if (displayFormatAttribute == null && dataTypeAttribute != null) 3 { 4 displayFormatAttribute = dataTypeAttribute.DisplayFormat; 5 } 6 var displayMetadata = context.DisplayMetadata; 7 8 // ConvertEmptyStringToNull 9 if (displayFormatAttribute != null) 10 { 11 displayMetadata.ConvertEmptyStringToNull = displayFormatAttribute. 12 ConvertEmptyStringToNull; 13 } 14 // DataTypeName 15 if (dataTypeAttribute == null) 16 { 17 if (displayFormatAttribute != null && !displayFormatAttribute.HtmlEncode) 18 { 19 displayMetadata.DataTypeName = DataType.Html.ToString(); 20 } 21 } 22 else 23 { 24 displayMetadata.DataTypeName = dataTypeAttribute.GetDataTypeName(); 25 } 26 27 var containerType = context.Key.ContainerType ?? context.Key.ModelType; 28 IStringLocalizer localizer = null; 29 if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null) 30 { 31 localizer = _localizationOptions.DataAnnotationLocalizerProvider(containerType, _stringLocalizerFactory); 32 } 33 34 // Description 35 if (displayAttribute != null) 36 { 37 if (localizer != null && 38 !string.IsNullOrEmpty(displayAttribute.Description) && 39 displayAttribute.ResourceType == null) 40 { 41 displayMetadata.Description = () => localizer[displayAttribute.Description]; 42 } 43 else 44 { 45 displayMetadata.Description = () => displayAttribute.GetDescription(); 46 } 47 } 48 49 // DisplayFormatString 50 if (displayFormatAttribute != null) 51 { 52 displayMetadata.DisplayFormatString = displayFormatAttribute.DataFormatString; 53 } 54 55 // DisplayName 56 // DisplayAttribute has precedence over DisplayNameAttribute. 57 if (displayAttribute?.GetName() == null) 58 { 59 if (displayNameAttribute != null) 60 { 61 if (localizer != null && 62 !string.IsNullOrEmpty(displayNameAttribute.DisplayName)) 63 { 64 displayMetadata.DisplayName = () => localizer[displayNameAttribute.DisplayName]; 65 } 66 else 67 { 68 displayMetadata.DisplayName = () => displayNameAttribute.DisplayName; 69 } 70 } 71 } 72 else 73 { 74 if (localizer != null && 75 !string.IsNullOrEmpty(displayAttribute.Name) && 76 displayAttribute.ResourceType == null) 77 { 78 displayMetadata.DisplayName = () => localizer[displayAttribute.Name]; 79 } 80 else 81 { 82 displayMetadata.DisplayName = () => displayAttribute.GetName(); 83 } 84 } 85 86 // EditFormatString 87 if (displayFormatAttribute != null && displayFormatAttribute.ApplyFormatInEditMode) 88 { 89 displayMetadata.EditFormatString = displayFormatAttribute.DataFormatString; 90 } 91 92 // IsEnum et cetera 93 var underlyingType = Nullable.GetUnderlyingType(context.Key.ModelType) ?? context.Key.ModelType; 94 var underlyingTypeInfo = underlyingType.GetTypeInfo(); 95 96 if (underlyingTypeInfo.IsEnum) 97 { 98 // IsEnum 99 displayMetadata.IsEnum = true; 100 101 // IsFlagsEnum 102 displayMetadata.IsFlagsEnum = underlyingTypeInfo.IsDefined(typeof(FlagsAttribute), inherit: false); 103 104 // EnumDisplayNamesAndValues and EnumNamesAndValues 105 // 106 // Order EnumDisplayNamesAndValues by DisplayAttribute.Order, then by the order of Enum.GetNames(). 107 // That method orders by absolute value, then its behavior is undefined (but hopefully stable). 108 // Add to EnumNamesAndValues in same order but Dictionary does not guarantee order will be preserved. 109 110 var groupedDisplayNamesAndValues = new List<KeyValuePair<EnumGroupAndName, string>>(); 111 var namesAndValues = new Dictionary<string, string>(); 112 113 IStringLocalizer enumLocalizer = null; 114 if (_localizationOptions.AllowDataAnnotationsLocalizationForEnumDisplayAttributes) 115 { 116 if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null) 117 { 118 enumLocalizer = _localizationOptions.DataAnnotationLocalizerProvider(underlyingType, _stringLocalizerFactory); 119 } 120 } 121 else 122 { 123 enumLocalizer = _stringLocalizerFactory?.Create(underlyingType); 124 } 125 126 var enumFields = Enum.GetNames(underlyingType) 127 .Select(name => underlyingType.GetField(name)) 128 .OrderBy(field => field.GetCustomAttribute<DisplayAttribute>(inherit: false)?.GetOrder() ?? 1000); 129 130 foreach (var field in enumFields) 131 { 132 var groupName = GetDisplayGroup(field); 133 var value = ((Enum)field.GetValue(obj: null)).ToString("d"); 134 135 groupedDisplayNamesAndValues.Add(new KeyValuePair<EnumGroupAndName, string>( 136 new EnumGroupAndName( 137 groupName, 138 () => GetDisplayName(field, enumLocalizer)), 139 value)); 140 namesAndValues.Add(field.Name, value); 141 } 142 143 displayMetadata.EnumGroupedDisplayNamesAndValues = groupedDisplayNamesAndValues; 144 displayMetadata.EnumNamesAndValues = namesAndValues; 145 } 146 147 // HasNonDefaultEditFormat 148 if (!string.IsNullOrEmpty(displayFormatAttribute?.DataFormatString) && 149 displayFormatAttribute?.ApplyFormatInEditMode == true) 150 { 151 // Have a non-empty EditFormatString based on [DisplayFormat] from our cache. 152 if (dataTypeAttribute == null) 153 { 154 // Attributes include no [DataType]; [DisplayFormat] was applied directly. 155 displayMetadata.HasNonDefaultEditFormat = true; 156 } 157 else if (dataTypeAttribute.DisplayFormat != displayFormatAttribute) 158 { 159 // Attributes include separate [DataType] and [DisplayFormat]; [DisplayFormat] provided override. 160 displayMetadata.HasNonDefaultEditFormat = true; 161 } 162 else if (dataTypeAttribute.GetType() != typeof(DataTypeAttribute)) 163 { 164 // Attributes include [DisplayFormat] copied from [DataType] and [DataType] was of a subclass. 165 // Assume the [DataType] constructor used the protected DisplayFormat setter to override its 166 // default. That is derived [DataType] provided override. 167 displayMetadata.HasNonDefaultEditFormat = true; 168 } 169 } 170 171 // HideSurroundingHtml 172 if (hiddenInputAttribute != null) 173 { 174 displayMetadata.HideSurroundingHtml = !hiddenInputAttribute.DisplayValue; 175 } 176 177 // HtmlEncode 178 if (displayFormatAttribute != null) 179 { 180 displayMetadata.HtmlEncode = displayFormatAttribute.HtmlEncode; 181 } 182 183 // NullDisplayText 184 if (displayFormatAttribute != null) 185 { 186 displayMetadata.NullDisplayText = displayFormatAttribute.NullDisplayText; 187 } 188 189 // Order 190 if (displayAttribute?.GetOrder() != null) 191 { 192 displayMetadata.Order = displayAttribute.GetOrder().Value; 193 } 194 195 // Placeholder 196 if (displayAttribute != null) 197 { 198 if (localizer != null && 199 !string.IsNullOrEmpty(displayAttribute.Prompt) && 200 displayAttribute.ResourceType == null) 201 { 202 displayMetadata.Placeholder = () => localizer[displayAttribute.Prompt]; 203 } 204 else 205 { 206 displayMetadata.Placeholder = () => displayAttribute.GetPrompt(); 207 } 208 }
-