深入ASP.NET MVC之十:服务器端Model Validation
ASP.NET MVC 3支持两大类型的验证:服务端和客户端脚本验证。本文先介绍服务端验证。在前文也介绍过,服务器端的验证是发生在模型绑定的时候,在DefaultModelBinder中有如下方法会触发验证:
internal void BindComplexElementalModel(ControllerContext controllerContext, ModelBindingContext bindingContext, object model) { // need to replace the property filter + model object and create an inner binding context ModelBindingContext newBindingContext = CreateComplexElementalModelBindingContext(controllerContext, bindingContext, model); // validation if (OnModelUpdating(controllerContext, newBindingContext)) { BindProperties(controllerContext, newBindingContext); OnModelUpdated(controllerContext, newBindingContext); }
其中,OnModelUpdated方法是真正触发验证逻辑的地方:
protected virtual void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext) { Dictionary<string, bool> startedValid = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); var res = ModelValidator.GetModelValidator(bindingContext.ModelMetadata, controllerContext).Validate(null); foreach (ModelValidationResult validationResult in res) { string subPropertyName = CreateSubPropertyName(bindingContext.ModelName, validationResult.MemberName); if (!startedValid.ContainsKey(subPropertyName)) { startedValid[subPropertyName] = bindingContext.ModelState.IsValidField(subPropertyName); } if (startedValid[subPropertyName]) { bindingContext.ModelState.AddModelError(subPropertyName, validationResult.Message); } } }
首先,它获得一个ModelValidator,这个ModelValidator是一个CompositeModelValidator,然后调用其Validate方法:
public override IEnumerable<ModelValidationResult> Validate(object container) { bool propertiesValid = true; foreach (ModelMetadata propertyMetadata in Metadata.Properties) { foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(ControllerContext)) { foreach (ModelValidationResult propertyResult in propertyValidator.Validate(Metadata.Model)) { propertiesValid = false; yield return new ModelValidationResult { MemberName = DefaultModelBinder.CreateSubPropertyName(propertyMetadata.PropertyName, propertyResult.MemberName), Message = propertyResult.Message }; } } } if (propertiesValid) { var typeValidators = Metadata.GetValidators(ControllerContext); foreach (ModelValidator typeValidator in typeValidators) { foreach (ModelValidationResult typeResult in typeValidator.Validate(container)) { yield return typeResult; } } } }
首先是查找model的property上的attribute,然后将其转换成一个IModelValidator对象,这里使用的是适配器模式,看下GetValidators方法,这是ModelMetaData的方法:
public virtual IEnumerable<ModelValidator> GetValidators(ControllerContext context) { return ModelValidatorProviders.Providers.GetValidators(this, context); }
ModelValidatorProviders定义如下:
public static class ModelValidatorProviders { private static readonly ModelValidatorProviderCollection _providers = new ModelValidatorProviderCollection() { new DataAnnotationsModelValidatorProvider(), new DataErrorInfoModelValidatorProvider(), new ClientDataTypeModelValidatorProvider() }; public static ModelValidatorProviderCollection Providers { get { return _providers; } } }
用的最多的是DataAnnotationsModelValidatorProvider,他是继承自AssociatedValidatorProvider, 看下它的GetValidators方法:
public override sealed IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context) { if (metadata.ContainerType != null && !String.IsNullOrEmpty(metadata.PropertyName)) { return GetValidatorsForProperty(metadata, context); } return GetValidatorsForType(metadata, context); }
首先判断当前的metadata是model的一个属性,还是model对象本身,分别调用两个不同的方法去获得model validator,这两个方法的不同之处只是在于通过反射分别获得放置在类型和属性(Proerty)上的属性(Attribute),然后调用抽象方法GetValidators,这个抽象方法在DataAnnotationsModelValidatorProvider中,实现如下
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) { _adaptersLock.EnterReadLock(); try { List<ModelValidator> results = new List<ModelValidator>(); // Add an implied [Required] attribute for any non-nullable value type, // unless they've configured us not to do that. if (AddImplicitRequiredAttributeForValueTypes && metadata.IsRequired && !attributes.Any(a => a is RequiredAttribute)) { attributes = attributes.Concat(new[] { new RequiredAttribute() }); } // Produce a validator for each validation attribute we find foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>()) { DataAnnotationsModelValidationFactory factory; if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory)) { factory = DefaultAttributeFactory; } results.Add(factory(metadata, context, attribute)); } // Produce a validator if the type supports IValidatableObject if (typeof(IValidatableObject).IsAssignableFrom(metadata.ModelType)) { DataAnnotationsValidatableObjectAdapterFactory factory; if (!ValidatableFactories.TryGetValue(metadata.ModelType, out factory)) { factory = DefaultValidatableFactory; } results.Add(factory(metadata, context)); } return results; } finally { _adaptersLock.ExitReadLock(); } }
首先是为不能为null的字段默认添加上required属性。然后遍历所有的标签中类型为ValidationAttribute的属性,对于每个属性,需要去找到一个DataAnnotationsModelValidationFactory ,这个factory是一个委托:
public delegate ModelValidator DataAnnotationsModelValidationFactory(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute);
这里可以认为是使用了Adapter模式,将通过属性的验证方法转换成ModelValidator。MVC针对各种不同的验证类型提供了一些对应的adapter factory(有删节):
internal static Dictionary<Type, DataAnnotationsModelValidationFactory> AttributeFactories = new Dictionary<Type, DataAnnotationsModelValidationFactory>() { { typeof(RangeAttribute), (metadata, context, attribute) => new RangeAttributeAdapter(metadata, context, (RangeAttribute)attribute) }, //…
};
这些对应的adapter factory主要为了提供客户端验证的支持,下面谈到客户端验证的时候再介绍,其余的功能和DefaultAttributeFactory类似,
internal static DataAnnotationsModelValidationFactory DefaultAttributeFactory = (metadata, context, attribute) => new DataAnnotationsModelValidator(metadata, context, attribute);
DataAnnotaionsModelValidator的主要方法如下:
public class DataAnnotationsModelValidator : ModelValidator { public DataAnnotationsModelValidator(ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute) : base(metadata, context) { if (attribute == null) { throw new ArgumentNullException("attribute"); } Attribute = attribute; } public override IEnumerable<ModelValidationResult> Validate(object container) { // Per the WCF RIA Services team, instance can never be null (if you have // no parent, you pass yourself for the "instance" parameter). ValidationContext context = new ValidationContext(container ?? Metadata.Model, null, null); context.DisplayName = Metadata.GetDisplayName(); ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context); if (result != ValidationResult.Success) { yield return new ModelValidationResult { Message = result.ErrorMessage }; } } }
事实上,诸如RangeAttributeAdapter此类的类都继承自DataAnnotationsModelValidator,对于服务端的验证,Validate方法来说,都是一样的。验证的结果统一表示为ValidationResult对象,被放置在bindingContext.ModelState中。渲染页面的时候,就会从ModelState中取出错误消息,显示在页面上。通过以上分析,可以知道服务端验证的过程大致是,对于一个model,首先获得他每个属性上的validation attribute,调用其验证方法,如果此属性是IValidatableObject类型的,同样调用其验证方法;如果对象本身有validationattribute,或者本身是IValidateableObject,则同样调用其对应的验证方法。