使用数据库进行 ASP.NET MVC 本地化
如何本地化 ASP.NET MVC 应用程序,并从数据库中读取所有文本,同时仍然使用 DataAnnotations。
引言
ASP.NET MVC 已经通过资源文件提供了对本地化的支持。但是,如果需要从数据库中读取文本,事情会变得更加复杂。特别是如果您仍然希望在模型中使用 DataAnnotations 来显示文本和验证消息。
背景
在模型中,DataAnnotations 可用于定义模型的标签和验证消息。让我们以一个在登录页面上使用的简单模型为例
public class LoginModel { [Required(ErrorMessage = "User name is required!")] [Display(Name = "User name")] public string UserName { get; set; } [Required(ErrorMessage = "Passwort is required!")] [Display(Name = "Password")] public string Password { get; set; } }
在相应的视图中,可以使用以下代码自动呈现一个标签、一个文本框和针对模型中 "UserName
" 属性的(最初不可见的)验证消息
@Html.LabelFor(m => m.UserName) @Html.TextBoxFor(m => m.UserName) @Html.ValidationMessageFor(m => m.UserName)
该视图在浏览器中可能如下所示
从数据库进行本地化
现在,由于我们希望从数据库中读取本地化文本,我们只需将文本 ID(或您在数据库中使用的任何唯一键)写入注释属性中
[Required(ErrorMessage = "27")] [Display(Name = "42")] public string UserName { get; set; }
要将 Display 属性的文本 ID 替换为数据库中的文本,我们需要创建自己的 MetadataProvider
public class MetadataProvider : AssociatedMetadataProvider { protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName) { var metadata = new ModelMetadata(this, containerType, modelAccessor, modelType, propertyName); if (propertyName != null) { var displayAttribute = attributes.OfType<DisplayAttribute>().FirstOrDefault(); if (displayAttribute != null) { int textId; if (Int32.TryParse(displayAttribute.Name, out textId)) { // TODO: get text from database metadata.DisplayName = "DB Text with id " + textId; } } } return metadata; } }
然后必须在 Global.asax.cs 的 Application_Start()
方法中注册此类
ModelMetadataProviders.Current = new MetadataProvider();
对于验证属性,情况会稍微复杂一些。我们需要的第一类是 ValidatorProvider
,它告诉 MVC 验证系统使用哪个类来执行模型验证。我们在 LocalizableModelValidatorProvider
类中的实现返回 LocalizableModelValidator
的一个实例。
public class LocalizableModelValidatorProvider : DataAnnotationsModelValidatorProvider { protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) { var validators = base.GetValidators(metadata, context, attributes); return validators.Select(validator => new LocalizableModelValidator(validator, metadata, context)).ToList(); } }
我们的验证提供程序也需要在 Global.asax.cs 的 Application_Start()
方法中注册
var provider = ModelValidatorProviders.Providers.FirstOrDefault(p => p.GetType() == typeof(DataAnnotationsModelValidatorProvider)); if (provider != null) { ModelValidatorProviders.Providers.Remove(provider); } ModelValidatorProviders.Providers.Add(new LocalizableModelValidatorProvider());
正如您所看到的,我们删除了类型为 DataAnnotationsModelValidatorProvider
的现有验证器提供程序,并用我们在 LocalizableModelValidatorProvider
类中的自己的实现替换了它。
我们需要的第二类是实际的 ModelValidatorProvider
,我们在 LocalizableModelValidatorProvider
类中实现它
public class LocalizableModelValidator : ModelValidator { private readonly ModelValidator innerValidator; public LocalizableModelValidator(ModelValidator innerValidator, ModelMetadata metadata, ControllerContext controllerContext) : base(metadata, controllerContext) { this.innerValidator = innerValidator; } public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() { var rules = innerValidator.GetClientValidationRules(); var modelClientValidationRules = rules as ModelClientValidationRule[] ?? rules.ToArray(); foreach (var rule in modelClientValidationRules) { int textId; if (Int32.TryParse(rule.ErrorMessage, out textId)) { // TODO: read text from database rule.ErrorMessage = "DB_Text_" + textId; } } return modelClientValidationRules; } public override IEnumerable<ModelValidationResult> Validate(object container) { // execute the inner validation which doesn't have localization var results = innerValidator.Validate(container); // convert the error message (text id) to the localized value return results.Select(result => { int textId; if (Int32.TryParse(result.Message, out textId)) { // TODO: read text from database result.Message = "DB text with id " + textId; } return new ModelValidationResult() { Message = result.Message }; }); } }
第一个方法 GetClientValidationRules()
负责客户端验证。每次视图呈现您的模型时,都会针对模型中具有验证属性的每个属性调用此方法。但是,如果您在 Web.config 中禁用客户端验证,则永远不会调用此方法。
第二个方法 ModelValidationResult()
负责服务器端验证。在表单发布回服务器后,将针对模型中具有验证属性的每个属性调用此方法。
使用代码
我的示例没有展示如何从数据库中读取文本。您会发现很多使用 ADO.NET 或 Entity Framework 的示例。为了获得最佳性能,请不要在每次需要读取单个文本时都访问数据库。最好构建自己的缓存,例如,将所有文本读入一个字典中,然后从那里获取它们。
历史
2016-04-13 | 初始版本 |