如何从ValidationAttribute中获取模型元数据?

时间:2010-03-02 00:30:58

标签: asp.net-mvc validation reflection data-annotations

MVC2带有一个名为“PropertiesMustMatchAttribute”的验证属性的好样本,它将比较两个字段以查看它们是否匹配。该属性的使用如下所示:

[PropertiesMustMatch("NewPassword", "ConfirmPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public class ChangePasswordModel
{
    public string NewPassword { get; set; }
    public string ConfirmPassword { get; set; }
}

该属性附加到模型类,并使用一些反射来完成其工作。您还会注意到此处的错误消息是直接指定的:“新密码和确认密码不匹配。”

如果您没有指定消息,则使用这样的代码生成默认消息(为清楚起见缩短了):

private const string _defaultErrorMessage = "'{0}' and '{1}' do not match.";
public override string FormatErrorMessage(string name)
{
    return String.Format(CultureInfo.CurrentUICulture, _defaultErrorMessage,
        OriginalProperty, ConfirmProperty);
}

问题在于,“OriginalProperty”和“ConfirmProperty”是属性中的硬编码字符串 - 本例中为“NewPassword”和“ConfirmPassword”。它们实际上并没有获得真实的模型元数据(例如,DisplayNameAttribute)来组合更灵活,可本地化的消息。我想要一个更普遍适用的比较属性,它使用已经指定的元数据显示名称信息等。

假设我不想为我的ValidationAttribute的每个实例创建自定义错误消息,这意味着我需要获取对模型元数据的引用(或者至少是我正在验证的模型类型)所以我可以获取元数据信息并在我的错误消息中使用它。

如何从属性内部验证模型的模型元数据?

(虽然我发现了一些问题,询问如何验证模型中的依赖字段,但没有一个答案包括正确处理错误消息。)

1 个答案:

答案 0 :(得分:4)

这实际上是how to get the instance decorated by an attribute, from the attribute问题的一个子集(类似问题here)。

不幸的是,简短的回答是:你不能。

属性元数据。属性不知道,不能知道它装饰的类或成员的任何信息。该类的下游消费者需要查找所述自定义属性并决定是否/何时/如何应用它们。

您必须将属性视为数据,而不是对象。虽然属性在技术上是类,但它们相当愚蠢,因为它们有一个关键约束:关于它们的一切必须在编译时定义。这实际上意味着他们无法访问任何运行时信息,除非他们公开了一个方法,该方法接受运行时实例,调用者决定调用它。

你可以做后者。您可以设计自己的属性,只要您控制验证器,就可以让验证器调用属性上的某个方法并让它做任何事情:

public abstract class CustomValidationAttribute : Attribute
{
    // Returns the error message, if any
    public abstract string Validate(object instance);
}

只要使用此类的人正确使用该属性,这将起作用:

public class MyValidator
{
    public IEnumerable<string> Validate(object instance)
    {
        if (instance == null)
            throw new ArgumentNullException("instance");
        Type t = instance.GetType();
        var validationAttributes = (CustomValidationAttribute[])Attribute
            .GetCustomAttributes(t, typeof(CustomValidationAttribute));
        foreach (var validationAttribute in validationAttributes)
        {
            string error = validationAttribute.Validate(instance);
            if (!string.IsNullOrEmpty(error))
                yield return error;
        }
    }
}

如果这是使用属性的方式,那么实现自己的属性就变得简单了:

public class PasswordValidationAttribute : CustomValidationAttribute
{
    public override string Validate(object instance)
    {
        ChangePasswordModel model = instance as ChangePasswordModel;
        if (model == null)
            return null;
        if (model.NewPassword != model.ConfirmPassword)
            return Resources.GetLocalized("PasswordsDoNotMatch");
        return null;
    }
}

这一切都很好,只是控制流程与您在原始问题中指定的内容相反。该属性对其应用的内容一无所知; 使用属性的验证器必须提供该信息(它可以轻松完成)。

当然,这不是验证实际上如何与MVC 2中的数据注释一起工作(除非它自我上次查看以来发生了重大变化)。我认为你不能用ValidationMessageFor和其他类似功能插入它。但是,嘿,在MVC 1中,无论如何我们必须编写所有自己的验证器。没有什么可以阻止您将DataAnnotations与您自己的自定义验证属性和验证器相结合,它只会涉及更多代码。无论何时编写验证码,都必须调用特殊的验证器。

这可能不是你正在寻找的答案,但遗憾的是它就是这样;验证属性无法知道应用它的类,除非验证器本身提供了该信息。