65.9K
CodeProject 正在变化。 阅读更多。
Home

使用数据注解属性的条件 ASP.NET MVC 验证

2013年2月7日

CPOL

4分钟阅读

viewsIcon

87700

downloadIcon

19

为数据注释验证属性声明条件逻辑

介绍 

本文讨论了一个简单的模块的设计,该模块允许为数据注解验证属性声明条件逻辑。 

背景

ASP.NET MVC 的一个出色功能是使用数据注解进行验证,该验证同时应用于客户端和服务器端。使用此功能的一个建议的好处是 DRY 原则(不要重复自己)。在项目中,有时您需要根据操作方法甚至数据进行条件验证。 数据注解验证方法的一个主要限制是它们没有任何条件逻辑。因此,如果您将字段标记为 RequiredAttribute,则所有操作方法都需要该字段。有不同的解决方法,也许最常见的一种涉及为每个操作使用视图模型。然而,这违背了 DRY 原则。

本文将讨论允许为数据注解验证属性添加条件逻辑的模块的设计和使用。 

设计 

主要类是 ConditionalAttribute,它本身继承自 Attribute。ConditionalAttribute 类允许定义数据注解属性并为其指定条件。  

[AttributeUsage(System.AttributeTargets.Property, AllowMultiple = true)]
public class ConditionalAttribute : Attribute
{
 public Type AttributeType { get; set; }
 public object[] ConstructorParam { get; set; }
 public bool Apply { get; set; }
 public string[] Actions { get; set; }
 public string[] Keys { get; set; }
 public object[] Values { get; set; }

 public ConditionalAttribute(string actionName, Type attributeType) 
 public ConditionalAttribute(string actionName, Type attributeType, object constructorParam) 
 public ConditionalAttribute(string actionName, Type attributeType, string key, string value) 
 public ConditionalAttribute(string actionName, Type attributeType, object constructorParam, string key, string value) 
 public ConditionalAttribute(string actionName, Type attributeType, object constructorParam, bool apply) 
 public ConditionalAttribute(string actionName, Type attributeType, object[] constructorParam, string key, string value, bool apply)
} 

CustomModelValidatorProvider 类继承自 DataAnnotationsModelValidatorProvider,并包含用于迭代条件属性、验证条件和添加数据注解验证属性的逻辑。  

protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
 List<Attribute> newAttributes = new List<Attribute>(attributes);
 var attrsCustom = GetCustomAttributes(metadata);
 if (attrsCustom != null)
 {
   var attrs = attrsCustom.OfType<ConditionalAttribute>().ToList();
   if (attrs != null)
   {
     foreach (var attr in attrs)
     {
      if (ApplyAttribute(attr, metadata, context, attributes))
      {
        Attribute objAttr = CreateAttribute(attr.AttributeType, attr) as Attribute;
        newAttributes.Add(objAttr);
      }
     }
   }
  }
  return base.GetValidators(metadata, context, newAttributes);
}  

IConditionalAttribute 接口有一个 Apply 方法,如果控制器实现了该接口,CustomModelValidatorProvider 类将调用该方法。然后,控制器可以决定是否应用验证属性。 

该模块允许调用任何验证属性,因为它在内部使用 Type 来创建实例并动态设置属性。    

 virtual protected object CreateAttribute(Type type, ConditionalAttribute attr)
 {
            object obj = Activator.CreateInstance(type,attr.ConstructorParam);
            var props = type.GetProperties();
            foreach (var prop in props)
            {
                object value = GetKeyValue(attr, prop.Name);
                if (value != null)
                    prop.SetValue(obj, value);
            }
            return obj;
 }   

这些类已在单个文件 CustomModelValidatorProvider.cs 中实现,以便于集成到项目中,并声明在 ConditionalAttributes 命名空间下。   

ConditionalAttribute 类的主要构造函数是:  

public ConditionalAttribute(string actionName, Type attributeType, object[] constructorParam, string key, string value, bool apply)   

actionName 参数允许指定属性应应用的 action 方法的名称。如果为空,则应用于所有操作。

attributeType 参数用于定义数据注解验证属性的类型。

constructorParam 用于指定实例化数据注解属性时可能需要的参数,例如 StringLengthAttribute 的长度。

keyvalue 参数指定属性的属性及其值,例如 ErrorMessage。

apply  参数反转了应用验证属性的逻辑。例如,要在除 Index 之外的所有操作方法上应用验证属性,请在 actionName 中设置 Index 并指定 apply=false。 

示例

[ConditionalAttribute("Modify",typeof(RequiredAttribute),"ErrorMessage","{0} is required field")]
public string Address { get; set; }   

此条件属性用于指定 RequiredAttribute 类型的验证属性。ErrorMessage 属性的值为 “{0} is a required field”。该方法应用于 Modify action 方法。

我们来看另一个例子: 

[ConditionalAttribute("Index",typeof(RemoteAttribute),Apply=false,ConstructorParam = new object[] {"NameExists", "Home" })]
public string Name {get; set; }  

上述语句仅在 action 方法名称不是 Index 时应用 RemoteAttribute 。它指定了构造函数参数 Action (NameExists) 和 Controller (Home)。

如果控制器实现了 IConditionalAttribute,则在所有条件都已评估后,Apply method 将被内部调用。该方法返回一个布尔值,该布尔值将控制是否应用验证属性。 

可以为属性应用多个条件属性。

public class Person
{
  [ConditionalAttribute(null, typeof(RequiredAttribute), "ErrorMessage", "{0} is a required field")]
  [ConditionalAttribute(null, typeof(StringLengthAttribute), 10, "ErrorMessage", "{0} cannot be more than 20 characters")]
  [ConditionalAttribute(null, typeof(RegularExpressionAttribute), @"[A-Za-z0-9]*", "ErrorMessage", "{0} should be alpahnumeric characters only")]
  [ConditionalAttribute("Index", typeof(RemoteAttribute), ConstructorParam = new object[] { "LoginIDExists", "Home" })]
  public string LoginID { get; set; }

  [ConditionalAttribute("Modify", typeof(RequiredAttribute), "ErrorMessage", "{0} is a required field")]
  [ConditionalAttribute("Index", typeof(RemoteAttribute), Apply = false, ConstructorParam = new object[] { "NameExists", "Home" })]
  public string Name { get; set; }

  [ConditionalAttribute("Modify", typeof(RequiredAttribute), "ErrorMessage", "{0} is a required field")]
  public string Address { get; set; }
}

项目

下载示例项目

附加的示例项目包含两个 action 方法。第一个是 Index,在该页面上,数据验证应用于登录 ID,而在姓名和地址上不应用任何验证,这是由于条件逻辑。在第二个 action 方法 Modify 中,应用了姓名和地址的条件验证。 

CustomModelValidatorProvider.cs 代码文件位于 App_Start 文件夹内。  

用法 

要开始在项目中使用 ConditionalAttribute ,请按照以下步骤操作:

CustomModelValidatorProvider.cs 复制到您的项目中 – 最好放在 App_Start 文件夹中。

global.asax.cs 文件中,将 CustomerModelValidatorProvider 添加到验证提供程序中。这在 Application_Start 方法中完成。您还需要添加 ConditionalAttributes 命名空间。 

ModelValidatorProviders.Providers.Clear();
ModelValidatorProviders.Providers.Add(new CustomModelValidatorProvider()); 

开始用条件属性注解对象模型。

为了有一个在条件属性评估期间调用的函数,请使控制器继承自 IConditionalAttribute 接口并实现 Apply 方法。 

增强功能

一些实用的增强功能。 

添加对非验证属性的支持,例如只读、显示名称等。 

使 ConditionalAttribute 更易于调用 – 目前它需要传递对象,因此类型安全性较低。最好的选择是将数据注解验证属性的实例及其参数传递给 ConditionalAttribute 构造函数,而不是类型。欢迎提供如何以通用方式执行此操作的建议。  

历史 

第一个版本 2013 年 2 月 6 日

© . All rights reserved.