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的每个实例创建自定义错误消息,这意味着我需要获取对模型元数据的引用(或者至少是我正在验证的模型类型)所以我可以获取元数据信息并在我的错误消息中使用它。
如何从属性内部验证模型的模型元数据?
(虽然我发现了一些问题,询问如何验证模型中的依赖字段,但没有一个答案包括正确处理错误消息。)
答案 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与您自己的自定义验证属性和验证器相结合,它只会涉及更多代码。无论何时编写验证码,都必须调用特殊的验证器。
这可能不是你正在寻找的答案,但遗憾的是它就是这样;验证属性无法知道应用它的类,除非验证器本身提供了该信息。