.net 3.5-使用数据注释对相关属性进行自定义模型验证
从现在开始,我使用了出色的FluentValidation库来验证我的模型类。 在Web应用程序中,我将其与jquery.validate插件结合使用以执行客户端验证。一个缺点是,许多验证逻辑在客户端重复,并且不再集中在单个位置。
因此,我正在寻找替代方案。 那里有许多示例显示了使用数据注释执行模型验证的过程。 看起来很有希望。我找不到的一件事是如何验证依赖于另一个属性值的属性。
让我们以以下模型为例:
public class Event
{
[Required]
public DateTime? StartDate { get; set; }
[Required]
public DateTime? EndDate { get; set; }
}
我想确保ValidationContext
大于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已在ASP.NET MVC 2中支持客户端验证和元数据。
尽管如此,还是要知道是否可以使用数据注释来验证相关属性。
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)在他的博客上有一个很好的示例,展示了如何也添加验证的客户端部分,尽管我不确定该示例在.NET 3.5和.NET 4.0中是否都适用。
我遇到了这个问题,最近开源了我的解决方案:[http://foolproof.codeplex.com/]
对于上面的示例,Foolproof的解决方案是:
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是一个新的非常有用的验证器,实际上并没有 部分 System.ComponentModel.DataAnnotations, 但已添加到 System.Web.Mvc DLL由团队提供。 同时 没有特别好的名字(唯一 它所做的比较是检查 平等,所以也许EqualTo将是 比较明显),很容易从中看到 该验证器检查的用法 一个属性的值等于 另一个财产的价值。 您可以 从代码中看到,该属性 接受一个字符串属性 另一个属性的名称 您正在比较。 经典用法 这种验证器就是我们 在这里使用它:密码 确认。
自问您的问题以来花了一段时间,但是如果您仍然喜欢元数据(至少有时是这样),下面还有另一种替代解决方案,它允许您为属性提供各种逻辑表达式:
[Required]
public DateTime? StartDate { get; set; }
[Required]
[AssertThat("StartDate != null && EndDate > StartDate")]
public DateTime? EndDate { get; set; }
它适用于服务器以及客户端。 更多详细信息可以在这里找到。
由于.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.
}
}
最后但并非最不重要的一点是,确保对象通过Context Scope传递:
Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
{
DataAnnotations.Validator.Validate(eventToValidate);
}
这有用吗?