namespace System.Web.ModelBinding { using System; using System.Collections.Generic; using System.Linq; public sealed class ModelValidationNode { public ModelValidationNode(ModelMetadata modelMetadata, string modelStateKey) : this(modelMetadata, modelStateKey, null) { } public ModelValidationNode(ModelMetadata modelMetadata, string modelStateKey, IEnumerable childNodes) { if (modelMetadata == null) { throw new ArgumentNullException("modelMetadata"); } if (modelStateKey == null) { throw new ArgumentNullException("modelStateKey"); } ModelMetadata = modelMetadata; ModelStateKey = modelStateKey; ChildNodes = (childNodes != null) ? childNodes.ToList() : new List(); } public ICollection ChildNodes { get; private set; } public ModelMetadata ModelMetadata { get; private set; } public string ModelStateKey { get; private set; } public bool ValidateAllProperties { get; set; } public bool SuppressValidation { get; set; } public event EventHandler Validated; public event EventHandler Validating; public void CombineWith(ModelValidationNode otherNode) { if (otherNode != null && !otherNode.SuppressValidation) { Validated += otherNode.Validated; Validating += otherNode.Validating; foreach (ModelValidationNode childNode in otherNode.ChildNodes) { ChildNodes.Add(childNode); } } } private void OnValidated(ModelValidatedEventArgs e) { EventHandler handler = Validated; if (handler != null) { handler(this, e); } } private void OnValidating(ModelValidatingEventArgs e) { EventHandler handler = Validating; if (handler != null) { handler(this, e); } } private object TryConvertContainerToMetadataType(ModelValidationNode parentNode) { if (parentNode != null) { object containerInstance = parentNode.ModelMetadata.Model; if (containerInstance != null) { Type expectedContainerType = ModelMetadata.ContainerType; if (expectedContainerType != null) { if (expectedContainerType.IsInstanceOfType(containerInstance)) { return containerInstance; } } } } return null; } public void Validate(ModelBindingExecutionContext modelBindingExecutionContext) { Validate(modelBindingExecutionContext, null /* parentNode */); } public void Validate(ModelBindingExecutionContext modelBindingExecutionContext, ModelValidationNode parentNode) { if (modelBindingExecutionContext == null) { throw new ArgumentNullException("modelBindingExecutionContext"); } if (SuppressValidation) { // no-op return; } // pre-validation steps ModelValidatingEventArgs eValidating = new ModelValidatingEventArgs(modelBindingExecutionContext, parentNode); OnValidating(eValidating); if (eValidating.Cancel) { return; } ValidateChildren(modelBindingExecutionContext); ValidateThis(modelBindingExecutionContext, parentNode); // post-validation steps ModelValidatedEventArgs eValidated = new ModelValidatedEventArgs(modelBindingExecutionContext, parentNode); OnValidated(eValidated); } private void ValidateChildren(ModelBindingExecutionContext modelBindingExecutionContext) { foreach (ModelValidationNode child in ChildNodes) { child.Validate(modelBindingExecutionContext, this); } if (ValidateAllProperties) { ValidateProperties(modelBindingExecutionContext); } } private void ValidateProperties(ModelBindingExecutionContext modelBindingExecutionContext) { // Based off CompositeModelValidator. ModelStateDictionary modelState = modelBindingExecutionContext.ModelState; // DevDiv Bugs #227802 - Caching problem in ModelMetadata requires us to manually regenerate // the ModelMetadata. object model = ModelMetadata.Model; ModelMetadata updatedMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, ModelMetadata.ModelType); foreach (ModelMetadata propertyMetadata in updatedMetadata.Properties) { // Only want to add errors to ModelState if something doesn't already exist for the property node, // else we could end up with duplicate or irrelevant error messages. string propertyKeyRoot = ModelBinderUtil.CreatePropertyModelName(ModelStateKey, propertyMetadata.PropertyName); if (modelState.IsValidField(propertyKeyRoot)) { foreach (ModelValidator propertyValidator in propertyMetadata.GetValidators(modelBindingExecutionContext)) { foreach (ModelValidationResult propertyResult in propertyValidator.Validate(model)) { string thisErrorKey = ModelBinderUtil.CreatePropertyModelName(propertyKeyRoot, propertyResult.MemberName); modelState.AddModelError(thisErrorKey, propertyResult.Message); } } } } } private void ValidateThis(ModelBindingExecutionContext modelBindingExecutionContext, ModelValidationNode parentNode) { ModelStateDictionary modelState = modelBindingExecutionContext.ModelState; if (!modelState.IsValidField(ModelStateKey)) { return; // short-circuit } object container = TryConvertContainerToMetadataType(parentNode); foreach (ModelValidator validator in ModelMetadata.GetValidators(modelBindingExecutionContext)) { foreach (ModelValidationResult validationResult in validator.Validate(container)) { string trueModelStateKey = ModelBinderUtil.CreatePropertyModelName(ModelStateKey, validationResult.MemberName); modelState.AddModelError(trueModelStateKey, validationResult.Message); } } } } }