.NET Core中不使用RESX文件实现作用域本地化的方案咨询
针对CMS多语言表单模型注解的解决方案
以下是几个适配你场景的可行方案,兼顾RCL模块的独立性和数据库动态翻译的需求:
1. 实现动态作用域的模型元数据提供器
放弃继承DefaultModelMetadataProvider(它会缓存元数据,导致无法动态更新),直接实现IModelMetadataProvider,在每次请求时根据当前区域性从数据库获取翻译:
public class ScopedModelMetadataProvider : IModelMetadataProvider { private readonly ITranslationService _translationService; private readonly IHttpContextAccessor _httpContextAccessor; public ScopedModelMetadataProvider(ITranslationService translationService, IHttpContextAccessor httpContextAccessor) { _translationService = translationService; _httpContextAccessor = httpContextAccessor; } public ModelMetadata GetMetadataForType(Type modelType) { var culture = GetCurrentCulture(); var metadata = new ModelMetadata( new ModelMetadataIdentity(modelType), null, () => _translationService.GetTranslation($"DisplayName_{modelType.Name}", culture) ); // 补充其他元数据属性(如描述、验证规则等) return metadata; } public ModelMetadata GetMetadataForProperty(Type containerType, PropertyInfo property) { var culture = GetCurrentCulture(); var displayNameKey = $"DisplayName_{containerType.Name}_{property.Name}"; var displayName = _translationService.GetTranslation(displayNameKey, culture); var metadata = new ModelMetadata( new ModelMetadataIdentity(containerType, property), null, () => displayName ); // 可在此处设置验证消息相关元数据,比如ErrorMessage return metadata; } private string GetCurrentCulture() { // 从HttpContext获取当前页面设置的区域性(比如路由、Cookie或CMS上下文) return _httpContextAccessor.HttpContext?.Items["PageCulture"]?.ToString() ?? "en-US"; } }
然后在Program.cs中注册为Scoped服务:
services.AddScoped<IModelMetadataProvider, ScopedModelMetadataProvider>();
这个方案的核心是每次请求都重新解析元数据,确保使用当前页面的区域性,同时支持从数据库动态获取翻译,无需依赖RESX文件。
2. 自定义本地化验证属性
对于自定义验证注解,避免硬编码错误消息,而是在验证时动态从数据库获取翻译。由于ValidationAttribute是单例,需要通过ValidationContext获取Scoped的翻译服务:
public class LocalizedRequiredAttribute : RequiredAttribute { private readonly string _translationKey; public LocalizedRequiredAttribute(string translationKey) { _translationKey = translationKey; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { var translationService = validationContext.GetService<ITranslationService>(); var cultureProvider = validationContext.GetService<ICmsCultureProvider>(); var currentCulture = cultureProvider.GetCurrentPageCulture(); ErrorMessage = translationService.GetTranslation(_translationKey, currentCulture); return base.IsValid(value, validationContext); } }
使用时直接指定翻译键:
public class ContactFormModel { [LocalizedRequired("Validation_Required_Name")] public string Name { get; set; } }
同时,对于框架自带的验证消息(比如必填、格式错误),可以通过配置ModelBindingMessageProvider动态替换:
services.AddMvc(options => { var serviceProvider = services.BuildServiceProvider(); var translationService = serviceProvider.GetRequiredService<ITranslationService>(); var cultureProvider = serviceProvider.GetRequiredService<ICmsCultureProvider>(); options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor( _ => translationService.GetTranslation("Validation_ValueCannotBeNull", cultureProvider.GetCurrentPageCulture()) ); // 其他默认验证消息同理配置 });
3. 自定义Tag Helper处理表单标签与验证消息
如果不想修改元数据或验证属性,可以通过Tag Helper动态替换表单元素的显示名和验证消息,完全绕过传统的元数据机制:
本地化Input标签的显示名
[HtmlTargetElement("input", Attributes = "asp-for")] public class LocalizedInputTagHelper : InputTagHelper { private readonly ITranslationService _translationService; private readonly ICmsCultureProvider _cultureProvider; public LocalizedInputTagHelper(IHtmlGenerator generator, ITranslationService translationService, ICmsCultureProvider cultureProvider) : base(generator) { _translationService = translationService; _cultureProvider = cultureProvider; } public override void Process(TagHelperContext context, TagHelperOutput output) { var culture = _cultureProvider.GetCurrentPageCulture(); var propertyKey = $"DisplayName_{For.Metadata.ContainerType.Name}_{For.Metadata.PropertyName}"; var displayName = _translationService.GetTranslation(propertyKey, culture); // 替换placeholder或关联的label文本 if (!string.IsNullOrEmpty(displayName)) { output.Attributes.SetAttribute("placeholder", displayName); // 如果页面中有对应的label标签,也可以通过context查找并修改 } base.Process(context, output); } }
本地化验证消息
[HtmlTargetElement("span", Attributes = "asp-validation-for")] public class LocalizedValidationMessageTagHelper : ValidationMessageTagHelper { private readonly ITranslationService _translationService; private readonly ICmsCultureProvider _cultureProvider; public LocalizedValidationMessageTagHelper(IHtmlGenerator generator, ITranslationService translationService, ICmsCultureProvider cultureProvider) : base(generator) { _translationService = translationService; _cultureProvider = cultureProvider; } public override void Process(TagHelperContext context, TagHelperOutput output) { base.Process(context, output); var culture = _cultureProvider.GetCurrentPageCulture(); var validationKey = $"Validation_{For.Metadata.PropertyName}"; var validationMessage = _translationService.GetTranslation(validationKey, culture); if (!string.IsNullOrEmpty(validationMessage)) { output.Content.SetContent(validationMessage); } } }
注册Tag Helper后,在RCL的视图中直接使用标准的asp-for和asp-validation-for标签,就能自动获取数据库中的翻译。
方案选择建议
- 如果需要全局统一处理所有模型元数据,优先选择动态模型元数据提供器;
- 如果只需要处理验证消息,自定义验证属性+ModelBindingMessageProvider更轻量;
- 如果希望最小化对现有代码的修改,自定义Tag Helper是最佳选择,无需修改模型或元数据配置。
内容的提问来源于stack exchange,提问作者user470249
相关产品推荐
相关产品推荐

