代码之家  ›  专栏  ›  技术社区  ›  Travis Illig

如何从validationattribute内部获取模型元数据?

  •  4
  • Travis Illig  · 技术社区  · 15 年前

    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 回复  |  直到 15 年前
        1
  •  4
  •   Community CDub    7 年前

    这实际上是 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 以及其他类似的功能。但是,在MVC1中,无论如何我们都必须编写我们自己的验证器。没有什么能阻止您将DataAnnotations与您自己的自定义验证属性和验证器组合在一起,它只会涉及更多的代码。在编写验证代码的任何地方都必须调用特殊的验证程序。

    这可能不是您要寻找的答案,但不幸的是,它是这样的;除非验证程序本身提供了该信息,否则验证属性无法知道它应用到的类。