DataAnnotation与自定义ResourceProvider

时间:2009-12-04 17:16:46

标签: c# asp.net-mvc localization resources

我创建了一个自定义ResourceProvider 来从数据库中提取本地化信息。我现在想使用DataAnnotation向模型添加验证。

DataAnnotation具有ErrorMessageResourceTypeErrorMessageResourceName属性,但ErrorMessageResourceType仅接受System.Type(即已编译的资源文件)

有没有办法让DataAnnotation使用自定义ResourceProvider?

3 个答案:

答案 0 :(得分:6)

我意识到这是一个老问题,但想补充一点。我发现自己处于相同的情况,似乎没有关于这个主题的任何文档/博客。尽管如此,我想出了一种使用自定义资源提供程序的方法,但有一点需要注意。需要注意的是,我在MVC应用程序中,所以我仍然可以使用HttpContext.GetLocalResourceObject()。这是asp.net用于本地化项目的方法。缺少资源对象并不会阻止您编写我们自己的解决方案,即使它是对DB表的直接查询。不过,我认为值得指出。

虽然我对以下解决方案并不十分满意,但它似乎有效。对于我想要使用的每个验证属性,我从所述属性继承并重载IsValid()。装饰看起来像这样:

[RequiredLocalized(ErrorMessageResourceType= typeof(ClassBeginValidated), ErrorMessageResourceName="Errors.GenderRequired")]
public string FirstName { get; set; } 

新属性如下所示:

public sealed class RequiredLocalized : RequiredAttribute {

    public override bool IsValid(object value) {

        if ( ! (ErrorMessageResourceType == null || String.IsNullOrWhiteSpace(ErrorMessageResourceName) )   ) {
            this.ErrorMessage = MVC_HtmlHelpers.Localize(this.ErrorMessageResourceType, this.ErrorMessageResourceName);
            this.ErrorMessageResourceType = null;
            this.ErrorMessageResourceName = null;
        }
        return base.IsValid(value);
    }
}

备注

  • 您需要使用派生属性(而非标准属性)
  • 来装饰代码
  • 我正在使用ErrorMessageResourceType传递要验证的类的类型。我的意思是,如果我在客户类并验证 FirstName 属性,我将通过 typeof(客户)。我这样做是因为在我的数据库后端我使用完整的类名(namespace + classname)作为键(与asp.net中使用页面URL的方式相同)。
    • MVC_HtmlHelpers.Localize 只是我的自定义资源提供程序的简单包装器

(半被盗)助手代码看起来像这样......

public static string Localize (System.Type theType, string resourceKey) {
    return Localize (theType, resourceKey, null);
}
public static string Localize (System.Type theType, string resourceKey, params object[] args) {
    string resource = (HttpContext.GetLocalResourceObject(theType.FullName, resourceKey) ?? string.Empty).ToString();
    return mergeTokens(resource, args);
}

private static string mergeTokens(string resource, object[] args)        {
    if (resource != null && args != null && args.Length > 0) {
        return string.Format(resource, args);
    }  else {
        return resource;
    }
}

答案 1 :(得分:3)

我使用了流畅的验证来实现这一目标。它节省了我很多时间。这就是我的Globalized验证器的样子。它确实意味着您不使用数据分析,但有时数据分析会变得有点大而且混乱。

以下是一个例子:

(Errors.Required,Labels.Email和Errors.AlreadyRegistered在我的blobal资源文件夹中。)

public class CreateEmployerValidator : AbstractValidator<CreateEmployerModel> {
    public RegisterUserValidator() { 
        RuleFor(m => m.Email)
            .NotEmpty()
            .WithMessage(String.Format(Errors.Required, new object[] { Labels.Email }))
            .EmailAddress()
            .WithMessage(String.Format(Errors.Invalid, new object[] { Labels.Email }))
            .Must(this.BeUniqueEmail)
            .WithMessage(String.Format(Errors.AlreadyRegistered,  new object[] { Labels.Email }));
    }

    public bool BeUniqueEmail(this IValidator validator, string email )  {
        //Database request to check if email already there?
        ...
    }    
}

就像我说的那样,这是从数据注释中移开的,只是因为我的方法已经有太多的注释了!

答案 2 :(得分:2)

我将添加我的发现,因为我不得不与此作斗争。也许它会帮助别人。

当您从RequiredAttribute派生时,它似乎打破了客户端验证。因此,为了解决这个问题,我实现了IClientValidatable并实现了GetClientValidationRules方法。 Resources.GetResources是我的包含HttpContext.GetGlobalResourceObject的静态帮助器方法。

自定义必需属性:

public class LocalizedRequiredAttribute : RequiredAttribute, IClientValidatable 
{
    public LocalizedRequiredAttribute(string resourceName)
    {
        this.ErrorMessage = Resources.GetResource(resourceName);
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        yield return new ModelClientValidationRule
        {
            ErrorMessage = this.ErrorMessage,
            ValidationType= "required"
        };
    }
}

用法:

[LocalizedRequired("SomeResourceName")]
public string SomeProperty { get; set; }

如果有人有兴趣,我的资源帮助:

public class Resources
{
    public static string GetResource(string resourceName)
    {
        string text = resourceName;
        if (System.Web.HttpContext.Current != null)
        {
            var context = new HttpContextWrapper(System.Web.HttpContext.Current);
            var globalResourceObject = context.GetGlobalResourceObject(null, resourceName);
            if (globalResourceObject != null)
                text = globalResourceObject.ToString();
        }

        return text;
    }
}