65.9K
CodeProject 正在变化。 阅读更多。
Home

使用数据库进行 ASP.NET MVC 本地化

starIconstarIconstarIconstarIconstarIcon

5.00/5 (14投票s)

2016 年 4 月 13 日

CPOL

2分钟阅读

viewsIcon

28771

downloadIcon

681

如何本地化 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.csApplication_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.csApplication_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 初始版本
© . All rights reserved.