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

ASP.NET MVC 使用 DataAnnotations 和 ModelValidators 进行模型验证示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.29/5 (6投票s)

2011年7月3日

CPOL

5分钟阅读

viewsIcon

93386

downloadIcon

3918

本文向读者介绍了 ASP.NET MVC 验证框架在复杂表单验证方面的灵活性,并简要说明了如何使用提供程序模型在内部创建 ModelMetadata。

引言

ASP.NET MVC 有一个非常好的验证框架。最近我一直在使用数据注解(data annotations)来玩弄 ASP.NET MVC 的验证功能,并获得了一些发现。我认为分享这些信息将对正在使用 MVC 验证框架的人有所帮助。

背景和概述

我最近一直在探索 ASP.NET MVC 验证框架,验证可以有多种类型,从简单到复杂,例如,必填字段验证、电子邮件验证、电话号码验证,或者一个字段取决于另一个字段,例如,电子邮件和确认电子邮件,密码和确认密码等,以及许多其他复杂的验证。

首先,我们如何强制执行模型上的验证规则,以及框架如何确保模型是否符合这些规则。在 ASP.NET 中,验证框架的基础是模型 **元数据(metadata)**。模型的元数据描述了在模型绑定时模型应如何进行验证。

模型属性可以用 `System.ComponentModel.DataAnnotations` 命名空间中的 **数据注解(data Annotations)** 进行修饰。注解定义了模型元数据,它们定义了当模型绑定到表单数据时模型上应该工作的验证,当然我们也可以使用相同的元数据来创建客户端验证规则,Jquery 已经有了验证插件。

ASP.NET 模型元数据系统使用 `ModelMetadata` 的多个实例来存储元数据,下面是 ModelMetadata 的结构。这里要注意的两个重要属性是 `containerType` 和 `Properties`。`ModelMetadata` 代表整个模型,以及模型中的每个属性。模型元数据由多个 `ModelMetadata` 对象实例表示。对于根模型对象,`Metadata containerType` 将为 `null`;对于模型中的属性,`containerType` 将为根模型对象。

public class ModelMetadata
    {       
	public virtual Dictionary<string,> AdditionalValues { get; }
	public Type ContainerType { get; }
	public virtual bool IsRequired { get; set; }
	public object Model { get; set; }
	public Type ModelType { get; }
	public virtual IEnumerable<modelmetadata> Properties { get; }
		.
		.
		.
		.		
}

ASP.NET MVC 有很多扩展点,它为我们提供了扩展和插入自定义验证的功能。它在大多数地方使用 **提供程序(provider)** 机制,便于自定义功能的即插即用。我们可以轻松地用自定义提供程序替换现有的提供程序。

一旦模型用 **注解(annotations)** 修饰,`ModelMetadataProvider` 就会帮助获取与模型关联的 `Metadata`。框架使用 `ModelMetadataProviders.Current` 来获取模型的 `ModelMetadataProvier`(它提供从应用了 `DataAnnotations` 构建的 `ModalMetaData` 类),如果我们想自定义 ASP.NET MVC 提供元数据的方式,我们随时可以拥有自己的提供程序。

如果我们想自定义默认的模型元数据提供程序,则从 `DataAnnotationsModelMetadataProvider` 扩展自定义的模型元数据提供程序,并将 `ModelMetadataProviders.Current` 属性设置为 `CustomModelMetadataProvider`。默认情况下,`ModelMetadataProvider` 是 `DataAnnotationsModelMetadataProvider`。

`Provider` 返回的 `ModelMetadata` 由 `ModelValidator` 进行验证。对于每个注解,如 `RequiredAttribute`、`RangeAttribute`,都有一个关联的验证器,如 `RequiredAdapter`、`RangeAdapter` 等。

Using the Code

我们能创建自己的自定义验证器吗?考虑验证 `ConfirmPassword` 应该与 `Password` 匹配,或者 `ConfirmEmail` 应该与 `Email` 匹配,这些验证取决于 `Model` 的两个属性值。例如,如果我们尝试通过注解的 `IsValid(object)` 方法而不是 `ModelValidator` 来验证这一点,那将行不通,因为我们没有模型上下文可用,而我们需要模型上下文来比较模型的两个属性。

这里我创建了一个名为 `SameAsAttribute` 的注解,并创建了关联的验证器 `SameAsValidator`。`SameAsAttribute` 为模型提供元数据,而 `SameAsValidator` 根据元数据验证模型。

上面的代码包含一个 `Employee` 模型以及关联的视图页面和控制器。

以下是验证失败时 `Employee` 视图页面的外观。

EmployeeView.JPG

以下是 `Employee` 模型的代码。

public class EmployeeModel
    {
        [Required]
        [DisplayName("Employee Id")]
        public int Id { get; set; }

        [Required]
        [DataType(DataType.Text)]
        [DisplayName("Name")]
        public string Name { get; set; }

        [Required]
        [DataType(DataType.Text)]
        [DisplayName("Address")]
        public string Address { get; set; }

        [Required]
        [DataType(DataType.PhoneNumber)]
        [DisplayName("Phone Number")]
        public int phoneNumber { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [DisplayName("Email")]
        public string Email { get; set; }

        [Required]
        [DataType(DataType.EmailAddress)]
        [SameAs("Email",ErrorMessage="It should be similar to Email")]
        [DisplayName("Confirm Email")]
        public string ConfirmEmail { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [DisplayName("Password")]
        public string Password { get; set; }

        [Required]
        [DataType(DataType.Password)]
        [DisplayName("Confirm Password")]
        [SameAs("Password", ErrorMessage = "It should be similar to Password")]
        public string ConfirmPassword { get; set; }

    }

`EmployeeModel` 的元数据由多个 `ModelMetadata` 类实例表示。`ModelMetadata` 是一个递归数据结构。`EmployeeModel` 由 `Root ModelMetadata` 对象表示,其 `containerType` 属性为 `null`,`Properties` 属性将包含一个 `ModelMetadata` 对象集合,每个对象指向属性 `ModelMetadata`(属性元数据),例如 `Id`、`Name` 等。对于属性元数据,`containerType` 将是 `EmployeeModel` 对象。

在上面的模型中,`Confirm Email`、`Confirm Password` 属性应用了 `SameAs` 注解,并且我们还设置了依赖属性的名称,当前属性应该与该属性匹配。以下是 `SameAs` 注解的代码。

public class SameAsAttribute: ValidationAttribute 
    {
        public string Property { get; set; }
        public SameAsAttribute(string Property)
        {
            this.Property = Property;

        }
        public override bool IsValid(object value)
        {
            //Any additional validation logic specific to the property can go here.
            return true;
        }       
    }

上面的代码以 `Property` 作为参数,这是依赖的 `Property`(`Email`),我们需要将当前 `property`(**Confirm Email**)与之匹配。以下是验证器的代码。

public class SameAsValidator :DataAnnotationsModelValidator
    {
        public SameAsValidator(ModelMetadata metadata, 
	ControllerContext context, ValidationAttribute attribute):base
		(metadata,context,attribute)
        {

        }   
       
        public override IEnumerable<modelvalidationresult> Validate(object container)
        {
           var dependentField= Metadata.ContainerType.GetProperty
			(((SameAsAttribute)Attribute).Property);
             var field= Metadata.ContainerType.GetProperty(this.Metadata.PropertyName);
           if (dependentField != null && field !=null)
           {
               object dependentValue = dependentField.GetValue(container, null);
               object value = field.GetValue(container, null);
               if ( (dependentValue != null && dependentValue.Equals(value)))
               {
                   if (!Attribute.IsValid(this.Metadata.Model))
                   {
                       yield return new ModelValidationResult 
				{ Message = ErrorMessage };
                   }
               }
               else

               yield return new ModelValidationResult { Message = ErrorMessage };
           }
        }
    }

在上面的代码中,`SameAsValidator` 继承自 `DataAnnotationsModelValidator`,并重写了 `Validate` 方法,该方法返回 `ModelValidationResult` 的枚举,该枚举将由框架附加到 `ModelState` 错误集合中。

如果我们不使用模型验证器进行验证,而是重写 `SameAsAttribute` 类的 `IsValid()` 方法,并在该函数中尝试应用验证逻辑,我们将无法这样做,因为我们将无法获得整个模型的上下文,我们只能获得应用了该注解的属性的值。但在这里,我们需要整个模型上下文来比较模型的两个属性。

在 `SameAsValidator` 类中,`Validate` 方法在应用于属性时,将模型对象(容器)作为参数传递给方法,因为属性对象的元数据容器是根模型对象。我们获取依赖属性和目标属性的值,比较它们,并准备一个 `ModelValidationResult` 对象的枚举列表,框架使用此结果显示验证摘要。

在给定的示例中,我们在 `Global.asax.cs` 文件中将 `SameAsAttribute` 数据注解注册到 `SameAsValidator`,并使用验证提供程序类。

DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(SameAsAttribute), 
	typeof(SameAsValidator)); 

关注点

为每个注解拥有一个单独的验证器,并能够将整个模型作为参数传递给验证器,这使我们能够执行任何类型的验证。

免责声明

本文向读者介绍了如何着手进行 ASP.NET MVC 模型验证。本文提供的示例不能用于直接的生产部署。

© . All rights reserved.