/// <summary>
        /// Recursively validate the properties of the given <paramref name="metadata"/>.
        /// </summary>
        /// <param name="metadata">The <see cref="ModelMetadata"/> for the object to validate.</param>
        /// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
        /// <returns>
        /// <see langword="true"/> if validation succeeds for all properties in <paramref name="metadata"/>;
        /// <see langword="false"/> otherwise.
        /// </returns>
        protected virtual bool ValidateProperties(ModelMetadata metadata, BodyModelValidatorContext validationContext)
        {
            if (metadata == null)
            {
                throw Error.ArgumentNull("metadata");
            }
            if (validationContext == null)
            {
                throw Error.ArgumentNull("validationContext");
            }

            bool          isValid       = true;
            PropertyScope propertyScope = new PropertyScope();

            validationContext.KeyBuilders.Push(propertyScope);
            foreach (ModelMetadata childMetadata in validationContext.MetadataProvider.GetMetadataForProperties(metadata.Model, metadata.RealModelType))
            {
                propertyScope.PropertyName = childMetadata.PropertyName;
                if (!ValidateNodeAndChildren(childMetadata, validationContext, metadata.Model, validators: null))
                {
                    isValid = false;
                }
            }
            validationContext.KeyBuilders.Pop();
            return(isValid);
        }
Example #2
0
        /// <summary>
        /// Validate a single node, not including its children.
        /// </summary>
        /// <param name="metadata">The <see cref="ModelMetadata"/>.</param>
        /// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
        /// <param name="container">The object to validate.</param>
        /// <param name="validators">The collection of <see cref="ModelValidator"/>s.</param>
        /// <returns>
        /// <see langword="true"/> if validation succeeds for the given <paramref name="metadata"/> and
        /// <paramref name="container"/>; <see langword="false"/> otherwise.
        /// </returns>
        protected virtual bool ShallowValidate(
            ModelMetadata metadata,
            BodyModelValidatorContext validationContext,
            object container,
            IEnumerable <ModelValidator> validators
            )
        {
            if (metadata == null)
            {
                throw Error.ArgumentNull("metadata");
            }
            if (validationContext == null)
            {
                throw Error.ArgumentNull("validationContext");
            }
            if (validators == null)
            {
                throw Error.ArgumentNull("validators");
            }

            bool   isValid  = true;
            string modelKey = null;

            // When the are no validators we bail quickly. This saves a GetEnumerator allocation.
            // In a large array (tens of thousands or more) scenario it's very significant.
            ICollection validatorsAsCollection = validators as ICollection;

            if (validatorsAsCollection != null && validatorsAsCollection.Count == 0)
            {
                return(isValid);
            }

            foreach (ModelValidator validator in validators)
            {
                foreach (ModelValidationResult error in validator.Validate(metadata, container))
                {
                    if (modelKey == null)
                    {
                        modelKey = validationContext.RootPrefix;
                        foreach (
                            IBodyModelValidatorKeyBuilder keyBuilder in validationContext.KeyBuilders.Reverse()
                            )
                        {
                            modelKey = keyBuilder.AppendTo(modelKey);
                        }
                    }
                    string errorKey = ModelBindingHelper.CreatePropertyModelName(
                        modelKey,
                        error.MemberName
                        );
                    validationContext.ModelState.AddModelError(errorKey, error.Message);
                    isValid = false;
                }
            }
            return(isValid);
        }
Example #3
0
        /// <summary>
        /// Determines whether the <paramref name="model"/> is valid and adds any validation errors to the <paramref name="actionContext"/>'s <see cref="ModelStateDictionary"/>
        /// </summary>
        /// <param name="model">The model to be validated.</param>
        /// <param name="type">The <see cref="Type"/> to use for validation.</param>
        /// <param name="metadataProvider">The <see cref="ModelMetadataProvider"/> used to provide the model metadata.</param>
        /// <param name="actionContext">The <see cref="HttpActionContext"/> within which the model is being validated.</param>
        /// <param name="keyPrefix">The <see cref="string"/> to append to the key for any validation errors.</param>
        /// <returns><c>true</c>if <paramref name="model"/> is valid, <c>false</c> otherwise.</returns>
        public bool Validate(
            object model,
            Type type,
            ModelMetadataProvider metadataProvider,
            HttpActionContext actionContext,
            string keyPrefix
            )
        {
            if (type == null)
            {
                throw Error.ArgumentNull("type");
            }

            if (metadataProvider == null)
            {
                throw Error.ArgumentNull("metadataProvider");
            }

            if (actionContext == null)
            {
                throw Error.ArgumentNull("actionContext");
            }

            if (model != null && !ShouldValidateType(model.GetType()))
            {
                return(true);
            }

            ModelValidatorProvider[] validatorProviders = actionContext
                                                          .GetValidatorProviders()
                                                          .ToArray();
            // Optimization : avoid validating the object graph if there are no validator providers
            if (validatorProviders == null || validatorProviders.Length == 0)
            {
                return(true);
            }

            ModelMetadata             metadata          = metadataProvider.GetMetadataForType(() => model, type);
            BodyModelValidatorContext validationContext = new BodyModelValidatorContext(
                actionContext.ModelState
                )
            {
                MetadataProvider = metadataProvider,
                ActionContext    = actionContext,
                ValidatorCache   = actionContext.GetValidatorCache(),
                RootPrefix       = keyPrefix
            };

            return(ValidateNodeAndChildren(
                       metadata,
                       validationContext,
                       container: null,
                       validators: null
                       ));
        }
        /// <summary>
        /// Recursively validate the elements of the <paramref name="model"/> collection.
        /// </summary>
        /// <param name="model">The <see cref="IEnumerable"/> instance containing the elements to validate.</param>
        /// <param name="validationContext">The <see cref="BodyModelValidatorContext"/>.</param>
        /// <returns>
        /// <see langword="true"/> if validation succeeds for all elements of <paramref name="model"/>;
        /// <see langword="false"/> otherwise.
        /// </returns>
        protected virtual bool ValidateElements(IEnumerable model, BodyModelValidatorContext validationContext)
        {
            if (model == null)
            {
                throw Error.ArgumentNull("model");
            }
            if (validationContext == null)
            {
                throw Error.ArgumentNull("validationContext");
            }

            bool          isValid         = true;
            Type          elementType     = GetElementType(model.GetType());
            ModelMetadata elementMetadata = validationContext.MetadataProvider.GetMetadataForType(null, elementType);

            ElementScope elementScope = new ElementScope()
            {
                Index = 0
            };

            validationContext.KeyBuilders.Push(elementScope);
            IEnumerable <ModelValidator> validators = validationContext.ActionContext.GetValidators(elementMetadata, validationContext.ValidatorCache);

            // if there are no validators or the object is null we bail out quickly
            // when there are large arrays of null, this will save a significant amount of processing
            // with minimal impact to other scenarios.
            bool anyValidatorsDefined = validators.Any();

            foreach (object element in model)
            {
                // If the element is non null, the recursive calls might find more validators.
                // If it's null, then a shallow validation will be performed.
                if (element != null || anyValidatorsDefined)
                {
                    elementMetadata.Model = element;

                    if (!ValidateNodeAndChildren(elementMetadata, validationContext, model, validators))
                    {
                        isValid = false;
                    }
                }

                elementScope.Index++;
            }
            validationContext.KeyBuilders.Pop();
            return(isValid);
        }
        protected virtual bool ValidateNodeAndChildren(
            ModelMetadata metadata,
            BodyModelValidatorContext validationContext,
            object container,
            IEnumerable <ModelValidator> validators)
        {
            // Recursion guard to avoid stack overflows
            RuntimeHelpers.EnsureSufficientExecutionStack();

            if (metadata == null)
            {
                throw Error.ArgumentNull("metadata");
            }
            if (validationContext == null)
            {
                throw Error.ArgumentNull("validationContext");
            }

            object model = null;

            try
            {
                model = metadata.Model;
            }
            catch
            {
                // Retrieving the model failed - typically caused by a property getter throwing
                // Being unable to retrieve a property is not a validation error - many properties can only be retrieved if certain conditions are met
                // For example, Uri.AbsoluteUri throws for relative URIs but it shouldn't be considered a validation error
                return(true);
            }

            bool isValid = true;

            if (validators == null)
            {
                validators = validationContext.ActionContext.GetValidators(metadata, validationContext.ValidatorCache);
            }

            // We don't need to recursively traverse the graph for null values
            if (model == null)
            {
                return(ShallowValidate(metadata, validationContext, container, validators));
            }

            // We don't need to recursively traverse the graph for types that shouldn't be validated
            Type modelType = model.GetType();

            if (TypeHelper.IsSimpleType(modelType) || !ShouldValidateType(modelType))
            {
                return(ShallowValidate(metadata, validationContext, container, validators));
            }

            // Check to avoid infinite recursion. This can happen with cycles in an object graph.
            if (validationContext.Visited.Contains(model))
            {
                return(true);
            }
            validationContext.Visited.Add(model);

            // Validate the children first - depth-first traversal
            IEnumerable enumerableModel = model as IEnumerable;

            if (enumerableModel == null)
            {
                isValid = ValidateProperties(metadata, validationContext);
            }
            else
            {
                isValid = ValidateElements(enumerableModel, validationContext);
            }
            if (isValid)
            {
                // Don't bother to validate this node if children failed.
                isValid = ShallowValidate(metadata, validationContext, container, validators);
            }

            // Pop the object so that it can be validated again in a different path
            validationContext.Visited.Remove(model);

            return(isValid);
        }