MethodInvocationValidator.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.ComponentModel.DataAnnotations;
  6. using System.Linq;
  7. using System.Reflection;
  8. using Abp.Collections.Extensions;
  9. using Abp.Configuration.Startup;
  10. using Abp.Dependency;
  11. using Abp.Reflection;
  12. namespace Abp.Runtime.Validation.Interception
  13. {
  14. /// <summary>
  15. /// This class is used to validate a method call (invocation) for method arguments.
  16. /// </summary>
  17. public class MethodInvocationValidator : ITransientDependency
  18. {
  19. private const int MaxRecursiveParameterValidationDepth = 8;
  20. protected MethodInfo Method { get; private set; }
  21. protected object[] ParameterValues { get; private set; }
  22. protected ParameterInfo[] Parameters { get; private set; }
  23. protected List<ValidationResult> ValidationErrors { get; }
  24. protected List<IShouldNormalize> ObjectsToBeNormalized { get; }
  25. private readonly IValidationConfiguration _configuration;
  26. private readonly IIocResolver _iocResolver;
  27. /// <summary>
  28. /// Creates a new <see cref="MethodInvocationValidator"/> instance.
  29. /// </summary>
  30. public MethodInvocationValidator(IValidationConfiguration configuration, IIocResolver iocResolver)
  31. {
  32. _configuration = configuration;
  33. _iocResolver = iocResolver;
  34. ValidationErrors = new List<ValidationResult>();
  35. ObjectsToBeNormalized = new List<IShouldNormalize>();
  36. }
  37. /// <param name="method">Method to be validated</param>
  38. /// <param name="parameterValues">List of arguments those are used to call the <paramref name="method"/>.</param>
  39. public virtual void Initialize(MethodInfo method, object[] parameterValues)
  40. {
  41. Check.NotNull(method, nameof(method));
  42. Check.NotNull(parameterValues, nameof(parameterValues));
  43. Method = method;
  44. ParameterValues = parameterValues;
  45. Parameters = method.GetParameters();
  46. }
  47. /// <summary>
  48. /// Validates the method invocation.
  49. /// </summary>
  50. public void Validate()
  51. {
  52. CheckInitialized();
  53. if (Parameters.IsNullOrEmpty())
  54. {
  55. return;
  56. }
  57. if (!Method.IsPublic)
  58. {
  59. return;
  60. }
  61. if (IsValidationDisabled())
  62. {
  63. return;
  64. }
  65. if (Parameters.Length != ParameterValues.Length)
  66. {
  67. throw new Exception("Method parameter count does not match with argument count!");
  68. }
  69. for (var i = 0; i < Parameters.Length; i++)
  70. {
  71. ValidateMethodParameter(Parameters[i], ParameterValues[i]);
  72. }
  73. if (ValidationErrors.Any())
  74. {
  75. ThrowValidationError();
  76. }
  77. foreach (var objectToBeNormalized in ObjectsToBeNormalized)
  78. {
  79. objectToBeNormalized.Normalize();
  80. }
  81. }
  82. protected virtual void CheckInitialized()
  83. {
  84. if (Method == null)
  85. {
  86. throw new AbpException("This object has not been initialized. Call Initialize method first.");
  87. }
  88. }
  89. protected virtual bool IsValidationDisabled()
  90. {
  91. if (Method.IsDefined(typeof(EnableValidationAttribute), true))
  92. {
  93. return false;
  94. }
  95. return ReflectionHelper.GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(Method) != null;
  96. }
  97. protected virtual void ThrowValidationError()
  98. {
  99. throw new AbpValidationException(
  100. "Method arguments are not valid! See ValidationErrors for details.",
  101. ValidationErrors
  102. );
  103. }
  104. /// <summary>
  105. /// Validates given parameter for given value.
  106. /// </summary>
  107. /// <param name="parameterInfo">Parameter of the method to validate</param>
  108. /// <param name="parameterValue">Value to validate</param>
  109. protected virtual void ValidateMethodParameter(ParameterInfo parameterInfo, object parameterValue)
  110. {
  111. if (parameterValue == null)
  112. {
  113. if (!parameterInfo.IsOptional &&
  114. !parameterInfo.IsOut &&
  115. !TypeHelper.IsPrimitiveExtendedIncludingNullable(parameterInfo.ParameterType, includeEnums: true))
  116. {
  117. ValidationErrors.Add(new ValidationResult(parameterInfo.Name + " is null!", new[] { parameterInfo.Name }));
  118. }
  119. return;
  120. }
  121. ValidateObjectRecursively(parameterValue, 1);
  122. }
  123. protected virtual void ValidateObjectRecursively(object validatingObject, int currentDepth)
  124. {
  125. if (currentDepth > MaxRecursiveParameterValidationDepth)
  126. {
  127. return;
  128. }
  129. if (validatingObject == null)
  130. {
  131. return;
  132. }
  133. if (_configuration.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject)))
  134. {
  135. return;
  136. }
  137. if (TypeHelper.IsPrimitiveExtendedIncludingNullable(validatingObject.GetType()))
  138. {
  139. return;
  140. }
  141. SetValidationErrors(validatingObject);
  142. // Validate items of enumerable
  143. if (IsEnumerable(validatingObject))
  144. {
  145. foreach (var item in (IEnumerable) validatingObject)
  146. {
  147. ValidateObjectRecursively(item, currentDepth + 1);
  148. }
  149. }
  150. // Add list to be normalized later
  151. if (validatingObject is IShouldNormalize)
  152. {
  153. ObjectsToBeNormalized.Add(validatingObject as IShouldNormalize);
  154. }
  155. if (ShouldMakeDeepValidation(validatingObject))
  156. {
  157. var properties = TypeDescriptor.GetProperties(validatingObject).Cast<PropertyDescriptor>();
  158. foreach (var property in properties)
  159. {
  160. if (property.Attributes.OfType<DisableValidationAttribute>().Any())
  161. {
  162. continue;
  163. }
  164. ValidateObjectRecursively(property.GetValue(validatingObject), currentDepth + 1);
  165. }
  166. }
  167. }
  168. protected virtual void SetValidationErrors(object validatingObject)
  169. {
  170. foreach (var validatorType in _configuration.Validators)
  171. {
  172. if (ShouldValidateUsingValidator(validatingObject, validatorType))
  173. {
  174. using (var validator = _iocResolver.ResolveAsDisposable<IMethodParameterValidator>(validatorType))
  175. {
  176. var validationResults = validator.Object.Validate(validatingObject);
  177. ValidationErrors.AddRange(validationResults);
  178. }
  179. }
  180. }
  181. }
  182. protected virtual bool ShouldValidateUsingValidator(object validatingObject, Type validatorType)
  183. {
  184. return true;
  185. }
  186. protected virtual bool ShouldMakeDeepValidation(object validatingObject)
  187. {
  188. // Do not recursively validate for enumerable objects
  189. if (validatingObject is IEnumerable)
  190. {
  191. return false;
  192. }
  193. var validatingObjectType = validatingObject.GetType();
  194. // Do not recursively validate for primitive objects
  195. if (TypeHelper.IsPrimitiveExtendedIncludingNullable(validatingObjectType))
  196. {
  197. return false;
  198. }
  199. return true;
  200. }
  201. private bool IsEnumerable(object validatingObject)
  202. {
  203. return
  204. validatingObject is IEnumerable &&
  205. !(validatingObject is IQueryable) &&
  206. !TypeHelper.IsPrimitiveExtendedIncludingNullable(validatingObject.GetType());
  207. }
  208. }
  209. }