使用数据注释自定义模型依赖属性的validation

由于现在我已经使用了优秀的FluentValidation库来validation我的模型类。 在Web应用程序中,我将它与jquery.validate插件一起使用来执行客户端validation。 一个缺点是validation逻辑的大部分在客户端重复,不再集中在一个地方。

出于这个原因,我正在寻找替代品。 有很多示例显示了使用数据注释来执行模型validation。 它看起来很有希望。 有一件事我找不到是如何validation依赖于另一个属性值的属性。

我们以下面的模型为例:

public class Event { [Required] public DateTime? StartDate { get; set; } [Required] public DateTime? EndDate { get; set; } } 

我想确保EndDate大于StartDate 。 我可以写一个自定义validation属性扩展ValidationAttribute为了执行自定义validation逻辑。 不幸的是,我找不到获得模型实例的方法:

 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属性,其中包含被validation的对象实例。 不幸的是,这个属性只是在.NET 4.0中添加的。 所以我的问题是:我可以在.NET 3.5 SP1中实现相同的function吗?


更新:

FluentValidation似乎已经支持 ASP.NET MVC 2中的客户端validation和元数据。

不过,如果可以使用数据注释来validation依赖属性,那还是很好的。

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”,整个模型实例被传递给它,所以你可以通过这种方式获得相关的属性值。 你可以很容易地遵循这个模式来创build一个date比较属性,甚至是更一般的比较属性。

Brad Wilson在他的博客上有一个很好的例子,展示了如何添加validation的客户端部分,尽pipe我不确定这个例子是否可以在.NET 3.5和.NET 4.0中工作。

我有这个问题,最近开源我的解决scheme: http : //foolproof.codeplex.com/

上面例子的万无一失的解决scheme是:

 public class Event { [Required] public DateTime? StartDate { get; set; } [Required] [GreaterThan("StartDate")] public DateTime? EndDate { get; set; } } 

而不是PropertiesMustMatch可以在MVC3中使用的CompareAttribute。 根据这个链接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是一个新的非常有用的validation器,它实际上并不是System.ComponentModel.DataAnnotations的一部分,但已被团队添加到System.Web.Mvc DLL中。 虽然不是特别好命名(唯一的比较是检查是否相等,所以也许E​​qualTo会更明显),从使用中很容易看出,这个validation器检查一个属性的值是否等于另一个属性的值。 您可以从代码中看到,该属性接受一个string属性,这是您正在比较的其他属性的名称。 这种types的validation器的经典用法就是我们在这里使用的:密码确认。

因为.NET 3.5的DataAnnotations的方法不允许你提供实际的对象validation或validation上下文,所以你将不得不做一些技巧来实现这一点。 我必须承认我不熟悉ASP.NET MVC,所以我不能说如何与MCV一起完成这个工作,但是可以尝试使用线程静态值来传递参数本身。 这是一个可能有效的例子。

首先创build一些“对象范围”,允许您传递对象而无需将它们传递给调用堆栈:

 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; } } 

接下来,创build你的validation器来使用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); } 

这有用吗?

自从你提出问题之后花了一点时间,但是如果你仍然喜欢元数据(至less有时候),下面还有另外一种可供select的解决scheme,它允许你为属性提供各种逻辑expression式:

 [Required] public DateTime? StartDate { get; set; } [Required] [AssertThat("StartDate != null && EndDate > StartDate")] public DateTime? EndDate { get; set; } 

它适用于服务器以及客户端。 更多细节可以在这里find 。