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());
}
}
}