在开发ASP.NETMVC应用程序时,经常需要从数据库中读取本地化文本。这不仅可以提高应用程序的灵活性,还可以方便地进行国际化。本文将介绍如何从数据库读取本地化文本,并使用DataAnnotations进行模型验证。
在模型中,DataAnnotations可以用来定义模型属性的标签和验证消息。以登录页面的简单模型为例:
public class LoginModel
{
[Required(ErrorMessage = "用户名是必需的!")]
[Display(Name = "用户名")]
public string UserName { get; set; }
[Required(ErrorMessage = "密码是必需的!")]
[Display(Name = "密码")]
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: 从数据库获取文本
metadata.DisplayName = "数据库中的文本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,在LocalizableModelValidator类中实现它:
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: 从数据库读取文本
rule.ErrorMessage = "数据库中的文本_" + textId;
}
}
return modelClientValidationRules;
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// 执行内部验证,没有本地化
var results = innerValidator.Validate(container);
// 将错误消息(文本ID)转换为本地化值
return results.Select(result =>
{
int textId;
if (Int32.TryParse(result.Message, out textId))
{
// TODO: 从数据库读取文本
result.Message = "数据库中的文本ID " + textId;
}
return new ModelValidationResult() { Message = result.Message };
});
}
}
第一个方法GetClientValidationRules()负责客户端验证。每当视图渲染模型时,它将被调用,用于模型中具有验证属性的每个属性。但是,如果您在Web.config中禁用客户端验证,这个方法将不会被调用。
第二个方法ModelValidationResult()负责服务器端验证。它将在表单提交回服务器后,被调用用于模型中具有验证属性的每个属性。