using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using Abp.Collections.Extensions; using Abp.Configuration.Startup; using Abp.Dependency; using Abp.Reflection; namespace Abp.Runtime.Validation.Interception { /// /// This class is used to validate a method call (invocation) for method arguments. /// public class MethodInvocationValidator : ITransientDependency { private const int MaxRecursiveParameterValidationDepth = 8; protected MethodInfo Method { get; private set; } protected object[] ParameterValues { get; private set; } protected ParameterInfo[] Parameters { get; private set; } protected List ValidationErrors { get; } protected List ObjectsToBeNormalized { get; } private readonly IValidationConfiguration _configuration; private readonly IIocResolver _iocResolver; /// /// Creates a new instance. /// public MethodInvocationValidator(IValidationConfiguration configuration, IIocResolver iocResolver) { _configuration = configuration; _iocResolver = iocResolver; ValidationErrors = new List(); ObjectsToBeNormalized = new List(); } /// Method to be validated /// List of arguments those are used to call the . public virtual void Initialize(MethodInfo method, object[] parameterValues) { Check.NotNull(method, nameof(method)); Check.NotNull(parameterValues, nameof(parameterValues)); Method = method; ParameterValues = parameterValues; Parameters = method.GetParameters(); } /// /// Validates the method invocation. /// public void Validate() { CheckInitialized(); if (Parameters.IsNullOrEmpty()) { return; } if (!Method.IsPublic) { return; } if (IsValidationDisabled()) { return; } if (Parameters.Length != ParameterValues.Length) { throw new Exception("Method parameter count does not match with argument count!"); } for (var i = 0; i < Parameters.Length; i++) { ValidateMethodParameter(Parameters[i], ParameterValues[i]); } if (ValidationErrors.Any()) { ThrowValidationError(); } foreach (var objectToBeNormalized in ObjectsToBeNormalized) { objectToBeNormalized.Normalize(); } } protected virtual void CheckInitialized() { if (Method == null) { throw new AbpException("This object has not been initialized. Call Initialize method first."); } } protected virtual bool IsValidationDisabled() { if (Method.IsDefined(typeof(EnableValidationAttribute), true)) { return false; } return ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault(Method) != null; } protected virtual void ThrowValidationError() { throw new AbpValidationException( "Method arguments are not valid! See ValidationErrors for details.", ValidationErrors ); } /// /// Validates given parameter for given value. /// /// Parameter of the method to validate /// Value to validate protected virtual void ValidateMethodParameter(ParameterInfo parameterInfo, object parameterValue) { if (parameterValue == null) { if (!parameterInfo.IsOptional && !parameterInfo.IsOut && !TypeHelper.IsPrimitiveExtendedIncludingNullable(parameterInfo.ParameterType, includeEnums: true)) { ValidationErrors.Add(new ValidationResult(parameterInfo.Name + " is null!", new[] { parameterInfo.Name })); } return; } ValidateObjectRecursively(parameterValue, 1); } protected virtual void ValidateObjectRecursively(object validatingObject, int currentDepth) { if (currentDepth > MaxRecursiveParameterValidationDepth) { return; } if (validatingObject == null) { return; } if (_configuration.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject))) { return; } if (TypeHelper.IsPrimitiveExtendedIncludingNullable(validatingObject.GetType())) { return; } SetValidationErrors(validatingObject); // Validate items of enumerable if (IsEnumerable(validatingObject)) { foreach (var item in (IEnumerable) validatingObject) { ValidateObjectRecursively(item, currentDepth + 1); } } // Add list to be normalized later if (validatingObject is IShouldNormalize) { ObjectsToBeNormalized.Add(validatingObject as IShouldNormalize); } if (ShouldMakeDeepValidation(validatingObject)) { var properties = TypeDescriptor.GetProperties(validatingObject).Cast(); foreach (var property in properties) { if (property.Attributes.OfType().Any()) { continue; } ValidateObjectRecursively(property.GetValue(validatingObject), currentDepth + 1); } } } protected virtual void SetValidationErrors(object validatingObject) { foreach (var validatorType in _configuration.Validators) { if (ShouldValidateUsingValidator(validatingObject, validatorType)) { using (var validator = _iocResolver.ResolveAsDisposable(validatorType)) { var validationResults = validator.Object.Validate(validatingObject); ValidationErrors.AddRange(validationResults); } } } } protected virtual bool ShouldValidateUsingValidator(object validatingObject, Type validatorType) { return true; } protected virtual bool ShouldMakeDeepValidation(object validatingObject) { // Do not recursively validate for enumerable objects if (validatingObject is IEnumerable) { return false; } var validatingObjectType = validatingObject.GetType(); // Do not recursively validate for primitive objects if (TypeHelper.IsPrimitiveExtendedIncludingNullable(validatingObjectType)) { return false; } return true; } private bool IsEnumerable(object validatingObject) { return validatingObject is IEnumerable && !(validatingObject is IQueryable) && !TypeHelper.IsPrimitiveExtendedIncludingNullable(validatingObject.GetType()); } } }