使用数据注释的依赖属性的自定义模型验证
Posted
技术标签:
【中文标题】使用数据注释的依赖属性的自定义模型验证【英文标题】:Custom model validation of dependent properties using Data Annotations 【发布时间】:2010-02-17 12:30:50 【问题描述】:从现在开始我就使用了出色的FluentValidation 库来验证我的模型类。在 Web 应用程序中,我也将它与 jquery.validate 插件结合使用来执行客户端验证。 一个缺点是大部分验证逻辑在客户端重复,不再集中在一个地方。
出于这个原因,我正在寻找替代方案。 there 中的 many 示例显示了使用数据注释来执行模型验证。它看起来很有希望。 我不知道的一件事是如何验证依赖于另一个属性值的属性。
我们以下面的模型为例:
public class Event
[Required]
public DateTime? StartDate get; set;
[Required]
public DateTime? EndDate get; set;
我想确保EndDate
大于StartDate
。我可以写一个自定义
验证属性扩展 ValidationAttribute 以执行自定义验证逻辑。不幸的是,我找不到获得
模型实例:
public class CustomValidationAttribute : ValidationAttribute
public override bool IsValid(object value)
// value represents the property value on which this attribute is applied
// but how to obtain the object instance to which this property belongs?
return true;
我发现CustomValidationAttribute 似乎可以完成这项工作,因为它具有包含正在验证的对象实例的ValidationContext
属性。不幸的是,此属性仅在 .NET 4.0 中添加。所以我的问题是:我可以在 .NET 3.5 SP1 中实现相同的功能吗?
更新:
似乎FluentValidation already supports ASP.NET MVC 2 中的客户端验证和元数据。
尽管数据注释是否可用于验证依赖属性,但还是很高兴知道。
【问题讨论】:
您或是否有人想出一种方法让 dataannotations 和 FluentValidation 在同一个类/模型上一起工作(用于验证)?如果是这样那就太棒了,我有一个关于这个与 FV 作者 Jeremy 讨论的帖子,你可以在这里查看:fluentvalidation.codeplex.com/Thread/View.aspx?ThreadId=212371 【参考方案1】:MVC2 附带一个示例“PropertiesMustMatchAttribute”,它展示了如何让 DataAnnotations 为您工作,它应该在 .NET 3.5 和 .NET 4.0 中都可以工作。该示例代码如下所示:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class PropertiesMustMatchAttribute : ValidationAttribute
private const string _defaultErrorMessage = "'0' and '1' do not match.";
private readonly object _typeId = new object();
public PropertiesMustMatchAttribute(string originalProperty, string confirmProperty)
: base(_defaultErrorMessage)
OriginalProperty = originalProperty;
ConfirmProperty = confirmProperty;
public string ConfirmProperty
get;
private set;
public string OriginalProperty
get;
private set;
public override object TypeId
get
return _typeId;
public override string FormatErrorMessage(string name)
return String.Format(CultureInfo.CurrentUICulture, ErrorMessageString,
OriginalProperty, ConfirmProperty);
public override bool IsValid(object value)
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(value);
object originalValue = properties.Find(OriginalProperty, true /* ignoreCase */).GetValue(value);
object confirmValue = properties.Find(ConfirmProperty, true /* ignoreCase */).GetValue(value);
return Object.Equals(originalValue, confirmValue);
当您使用该属性时,而不是将其放在模型类的属性上,而是将其放在类本身上:
[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;
当在您的自定义属性上调用“IsValid”时,整个模型实例都会传递给它,因此您可以通过这种方式获取相关属性值。您可以轻松地按照此模式创建日期比较属性,甚至是更通用的比较属性。
Brad Wilson has a good example on his blog 还展示了如何添加验证的客户端部分,但我不确定该示例是否适用于 .NET 3.5 和 .NET 4.0。
【讨论】:
我已经尝试过了,但我永远无法在我的 aspx 页面/视图上显示验证错误。我尝试使用空字符串调用validationmessagefor,也尝试使用验证摘要,但它没有显示在那里(就像在propertiesmustmatch示例中那样) 我浪费了好几个小时试图让它工作并认为我的代码是错误的,直到我终于意识到当我看到这篇文章时我根本没有正确测试它:***.com/questions/3586324/…(基本上字段/属性级别验证首先触发,因此您需要在它触发您的类级别属性 isvalid() 方法之前对其进行完全验证。 我后来在这里找到了关于字段验证问题后的类验证的更好的帖子:***.com/questions/3099397/…【参考方案2】:我遇到了这个问题,最近开源了我的解决方案: http://foolproof.codeplex.com/
上面例子的万无一失的解决方案是:
public class Event
[Required]
public DateTime? StartDate get; set;
[Required]
[GreaterThan("StartDate")]
public DateTime? EndDate get; set;
【讨论】:
我认为 GreaterThan 日期验证仅适用于美国格式日期【参考方案3】:可以在 MVC3 中使用的 CompareAttribute 代替 PropertiesMustMatch。根据这个链接http://devtrends.co.uk/blog/the-complete-guide-to-validation-in-asp.net-mvc-3-part-1:
public class RegisterModel
// skipped
[Required]
[ValidatePasswordLength]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password get; set;
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation do not match.")]
public string ConfirmPassword get; set;
CompareAttribute 是一个新的、非常有用的验证器,实际上并不是 部分 System.ComponentModel.DataAnnotations, 但已添加到 团队的 System.Web.Mvc DLL。同时 不是特别好命名(唯一的 它所做的比较是为了检查 平等,所以也许 EqualTo 是 更明显),很容易从 此验证器检查的用法 一个财产的价值等于 另一个财产的价值。你可以 从代码中可以看出,该属性 接受一个字符串属性,它是 其他属性的名称 你在比较。经典用法 这种类型的验证器就是我们 在这里使用它:密码 确认。
【讨论】:
【参考方案4】:您的问题被问到已经过了一段时间,但如果您仍然喜欢元数据(至少有时),下面还有另一种替代解决方案,它允许您为属性提供各种逻辑表达式:
[Required]
public DateTime? StartDate get; set;
[Required]
[AssertThat("StartDate != null && EndDate > StartDate")]
public DateTime? EndDate get; set;
它适用于服务器端和客户端。更多详情can be found here.
【讨论】:
非常感谢这个库对大多数事情都非常有用。【参考方案5】:由于 .NET 3.5 的 DataAnnotations 的方法不允许您提供已验证的实际对象或验证上下文,因此您必须采取一些技巧来完成此操作。我必须承认我不熟悉 ASP.NET MVC,所以我不能说如何与 MCV 完全结合使用,但您可以尝试使用线程静态值来传递参数本身。这是一个可能有用的例子。
首先创建某种“对象范围”,允许您传递对象而无需通过调用堆栈传递它们:
public sealed class ContextScope : IDisposable
[ThreadStatic]
private static object currentContext;
public ContextScope(object context)
currentContext = context;
public static object CurrentContext
get return context;
public void Dispose()
currentContext = null;
接下来,创建您的验证器以使用 ContextScope:
public class CustomValidationAttribute : ValidationAttribute
public override bool IsValid(object value)
Event e = (Event)ObjectContext.CurrentContext;
// validate event here.
最后但并非最不重要的一点是,确保对象通过 ContextScope:
Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
DataAnnotations.Validator.Validate(eventToValidate);
这有用吗?
【讨论】:
史蒂文,这看起来不错。我唯一要修改的是将当前上下文存储到 HttpContext 中,而不是使用ThreadStatic
。我只是在 ASP.NET 应用程序中避免它。
您能解释一下为什么您认为我们应该在 ASP.NET 应用程序中避免这种情况。我在自己的生产应用程序中使用了这个结构,所以我很感兴趣为什么这是不好的。
互联网上有很多文章为什么这是不好的。这是一个:hanselman.com/blog/… ThreadStatic 的问题在于,在 ASP.NET 中,您无法控制线程的生命周期,并且在重用线程时,在某些情况下变量可能会被修改。如果您使用异步页面和控制器,事情会变得更加丑陋。例如,一个请求可能在一个线程上开始并在另一个线程上结束。因此,在 ASP.NET 中,唯一 拥有真正的按请求存储的方法是 HttpContext。
是的,我猜这就是你要去的地方。您对异步页面和控制器是绝对正确的。但是,由于使用它的“上下文”,我并不担心在这种情况下使用 ThreadStatic。 ContextScope 在单个方法调用中使用,可能在您的业务层的某个地方。在这种情况下,ASP.NET 无法将您的方法调用撕成碎片(在您执行该特定方法时切换线程)。当然,您不应该将 ContextScope 存储为 Page 的私有成员,这确实会带来麻烦。 ...
所以在这种情况下,是的,你必须小心滥用,我同意你不应该在你的 ASP.NET Web 应用程序项目中使用它,但是你可以愉快地在您的 Web 应用程序的业务层中使用它。但是当您仍然担心团队中的开发人员可能会滥用此构造时,只需将“currentContext =”的出现更改为“HttpContext.Current.Items[typeof(ContextScope)] =”即可。以上是关于使用数据注释的依赖属性的自定义模型验证的主要内容,如果未能解决你的问题,请参考以下文章
在 XAML 中调用时,如何使我的自定义依赖项属性排序到顶部?