45

从现在开始,我使用了出色的FluentValidation 库来验证我的模型类。在 Web 应用程序中,我也将它与jquery.validate插件一起使用来执行客户端验证。一个缺点是大部分验证逻辑在客户端重复,不再集中在一个地方。

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

让我们以以下模型为例:

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似乎已经支持ASP.NET MVC 2 中的客户端验证和元数据。

不过,如果可以使用数据注释来验证依赖属性,还是很高兴知道的。

4

5 回答 5

29

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。

于 2010-03-02T00:16:44.800 回答
15

我遇到了这个问题,最近开源了我的解决方案:http: //foolproof.codeplex.com/

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

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

    [Required]
    [GreaterThan("StartDate")]
    public DateTime? EndDate { get; set; }
}
于 2010-04-06T03:03:00.923 回答
7

可以在 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 可能会更明显),从使用中很容易看出这个验证器检查一个属性的值是否等于另一个属性的值. 您可以从代码中看到,该属性接受一个字符串属性,该属性是您正在比较的另一个属性的名称。这种类型的验证器的经典用法是我们在这里使用它:密码确认。

于 2011-05-23T04:28:55.323 回答
4

自从提出您的问题以来,花了一点时间,但如果您仍然喜欢元数据(至少有时),下面还有另一种替代解决方案,它允许您为属性提供各种逻辑表达式:

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

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

于 2014-07-17T21:11:45.837 回答
3

因为 .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);
}

这有用吗?

于 2010-02-17T17:00:58.507 回答