Validation 使用数据注释对依赖属性进行自定义模型验证

Validation 使用数据注释对依赖属性进行自定义模型验证,validation,.net-3.5,asp.net-mvc-2,data-annotations,Validation,.net 3.5,Asp.net Mvc 2,Data Annotations,从现在起,我一直使用优秀的 库来验证我的模型类。在web应用程序中,我将其与插件结合使用,以执行客户端验证。 一个缺点是大部分验证逻辑在客户端重复,不再集中在一个地方 出于这个原因,我正在寻找另一种选择。下面的示例展示了如何使用数据注释来执行模型验证。看起来很有希望。 有一件事我找不到,那就是如何验证依赖于另一个属性值的属性 让我们以以下模型为例: public class Event { [Required] public DateTime? StartDate { get;

从现在起,我一直使用优秀的 库来验证我的模型类。在web应用程序中,我将其与插件结合使用,以执行客户端验证。 一个缺点是大部分验证逻辑在客户端重复,不再集中在一个地方

出于这个原因,我正在寻找另一种选择。下面的示例展示了如何使用数据注释来执行模型验证。看起来很有希望。 有一件事我找不到,那就是如何验证依赖于另一个属性值的属性

让我们以以下模型为例:

public class Event
{
    [Required]
    public DateTime? StartDate { get; set; }
    [Required]
    public DateTime? EndDate { get; set; }
}
我想确保
EndDate
大于
StartDate
。我可以写一个习惯 验证属性扩展以执行自定义验证逻辑。不幸的是,我找不到一种方法来获得 模型实例:

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;
    }
}
我发现似乎完成了这项工作,因为它具有包含正在验证的对象实例的
ValidationContext
属性。不幸的是,此属性仅在.NET 4.0中添加。所以我的问题是:我能在.NET3.5SP1中实现相同的功能吗


更新:

ASP.NET MVC 2中的客户端验证和元数据


尽管如此,最好知道是否可以使用数据注释来验证依赖属性。

因为.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.
    }
}
Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
{
    DataAnnotations.Validator.Validate(eventToValidate);
}
最后但并非最不重要的一点是,确保对象通过ContextScope:

public class CustomValidationAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
         Event e = (Event)ObjectContext.CurrentContext;

         // validate event here.
    }
}
Event eventToValidate = [....];
using (var scope new ContextScope(eventToValidate))
{
    DataAnnotations.Validator.Validate(eventToValidate);
}
这有用吗?

MVC2附带了一个“PropertiesMustMatchAttribute”示例,该示例演示了如何让DataAnnotation在.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”时,整个模型实例将传递给它,这样您就可以通过这种方式获得依赖属性值。您可以很容易地按照此模式创建一个日期比较属性,甚至是一个更通用的比较属性


演示如何添加验证的客户端部分,尽管我不确定该示例是否适用于.NET 3.5和.NET 4.0。

我遇到了这个问题,最近公开了我的解决方案:

对于上述示例,万无一失的解决方案是:

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

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

而不是属性必须匹配可在MVC3中使用的CompareAttribute。根据这个链接:

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

它既适用于服务器端,也适用于客户端。更多细节。

Steven,这看起来不错。我唯一要修改的是将当前上下文存储到HttpContext中,而不是使用
ThreadStatic
。我只想在ASP.NET应用程序中避免这种情况。您能解释一下为什么您认为我们应该在ASP.NET应用程序中避免这种情况吗。我在自己的产品应用程序中使用了这个结构,所以我非常感兴趣为什么这样不好。互联网上有很多文章为什么这样不好。这里有一个问题:ThreadStatic的问题是,在ASP.NET中,您无法控制线程的生命周期,当线程被重用时,可能会修改变量。如果使用异步页面和控制器,事情会变得更加糟糕。例如,请求可能在一个线程上启动,在另一个线程上完成。所以在ASP.NET中,拥有真正的每请求存储的唯一方法是HttpContext。是的,我猜这就是你要做的。关于异步页面和控制器,您完全正确。然而,我并不担心在这个场景中使用th ThreadStatic,因为它使用的是“上下文”。ContextScope用于单个方法调用,可能在业务层的某个地方。在这种情况下,ASP.NET无法将方法调用撕成碎片(在执行特定方法时切换线程)。当然,您不应该将ContextScope存储为页面的私有成员,这确实会带来麻烦。。。因此,在这种情况下,您必须小心滥用,我同意您不应该在ASP.NET web应用程序项目中使用它,但您可以在web应用程序的业务层中愉快地使用它。但当您仍然担心团队中的开发人员可能会滥用此结构时,siml