ASP.NET Core切换语言时IValidationMetadataProvider不返回验证信息
问题背景
实现了LocalizedValidationMetadataProvider类(实现IValidationMetadataProvider接口),代码如下:
public class LocalizedValidationMetadataProvider : IValidationMetadataProvider { public LocalizedValidationMetadataProvider() { } public void CreateValidationMetadata(ValidationMetadataProviderContext context) { var propertyName = context.Key.Name; if (string.IsNullOrEmpty(propertyName)) return; 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 != null) { var errorName = tAttr.GetType().Name; var fallbackName = errorName + "_ValidationError"; var name = tAttr.ErrorMessage ?? fallbackName; var localized = Localizer.GetLocalization(name); var text = localized; tAttr.ErrorMessage = text; tAttr.ErrorMessageResourceName = ""; } } } }
当前代码仅在应用启动时加载验证信息,切换语言后验证文本无变化,但DisplayName已正常更新。
原因分析
IValidationMetadataProvider的CreateValidationMetadata方法仅在应用启动阶段的元数据初始化过程中执行一次,此时赋值给ValidationAttribute.ErrorMessage的是固定的本地化字符串,后续请求切换语言时,不会重新执行该方法更新错误文本。而DisplayName的本地化通常是在请求处理阶段动态获取的,因此能正常跟随语言切换更新。
解决方案
核心思路是让验证错误文本在每次请求验证时动态获取当前语言的本地化内容,而非在启动阶段固定赋值。具体有两种实现方式:
方式一:利用框架内置的资源本地化机制
修改LocalizedValidationMetadataProvider的逻辑,不直接设置ErrorMessage,而是配置ErrorMessageResourceName和ErrorMessageResourceType,让框架在验证时自动根据当前请求文化加载资源:
public class LocalizedValidationMetadataProvider : IValidationMetadataProvider { private readonly Type _resourceType; public LocalizedValidationMetadataProvider(Type resourceType) { _resourceType = resourceType; } public void CreateValidationMetadata(ValidationMetadataProviderContext context) { var propertyName = context.Key.Name; if (string.IsNullOrEmpty(propertyName)) return; // 自动为值类型添加RequiredAttribute的逻辑保留 if (context.Key.ModelType.GetTypeInfo().IsValueType && Nullable.GetUnderlyingType(context.Key.ModelType.GetTypeInfo()) == null && !context.ValidationMetadata.ValidatorMetadata.Any(m => m is RequiredAttribute)) { var requiredAttr = new RequiredAttribute(); context.ValidationMetadata.ValidatorMetadata.Add(requiredAttr); } foreach (var attribute in context.ValidationMetadata.ValidatorMetadata) { var tAttr = attribute as ValidationAttribute; if (tAttr != null) { var errorName = tAttr.GetType().Name; var fallbackName = errorName + "_ValidationError"; var resourceName = tAttr.ErrorMessage ?? fallbackName; // 配置资源属性,让框架动态获取本地化文本 tAttr.ErrorMessageResourceName = resourceName; tAttr.ErrorMessageResourceType = _resourceType; tAttr.ErrorMessage = null; // 清空固定错误消息,强制使用资源 } } } }
同时确保项目中已正确配置本地化资源:
- 在项目中创建
Resources目录,添加对应语言的资源文件(如ValidationResources.resx、ValidationResources.zh-CN.resx),并在资源文件中添加对应resourceName的键值对。 - 在Startup中配置本地化服务:
services.AddLocalization(options => options.ResourcesPath = "Resources"); services.Configure<RequestLocalizationOptions>(options => { var supportedCultures = new[] { new CultureInfo("en"), new CultureInfo("zh-CN") }; options.DefaultRequestCulture = new RequestCulture("en"); options.SupportedCultures = supportedCultures; options.SupportedUICultures = supportedCultures; }); // 注册本地化元数据提供器,传入你的资源类型 services.AddSingleton<IValidationMetadataProvider>(new LocalizedValidationMetadataProvider(typeof(ValidationResources)));
方式二:自定义本地化验证属性基类
如果使用自定义的本地化工具(而非框架内置资源),可以创建一个基类,在FormatErrorMessage方法中动态获取当前请求的本地化文本:
public abstract class LocalizedValidationAttribute : ValidationAttribute { private readonly IHttpContextAccessor _httpContextAccessor; protected LocalizedValidationAttribute(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } protected abstract string GetResourceKey(); public override string FormatErrorMessage(string name) { // 获取当前请求的本地化工具实例 var localizer = _httpContextAccessor.HttpContext.RequestServices.GetService<YourCustomLocalizer>(); var resourceKey = GetResourceKey(); return localizer.GetLocalization(resourceKey); } } // 示例:本地化的RequiredAttribute public class LocalizedRequiredAttribute : LocalizedValidationAttribute { public LocalizedRequiredAttribute(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor) { } protected override string GetResourceKey() { return "RequiredAttribute_ValidationError"; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value == null || (value is string str && string.IsNullOrEmpty(str))) { return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } return ValidationResult.Success; } }
然后在LocalizedValidationMetadataProvider中添加自定义的本地化验证属性,替换原有的RequiredAttribute,并确保所有验证属性都继承自LocalizedValidationAttribute。
验证效果
修改完成后,切换请求的语言文化(如通过Accept-Language头或路由参数),验证错误消息会自动跟随当前语言更新,和DisplayName的表现保持一致。
内容的提问来源于stack exchange,提问作者Dua'a Shloul

