/src/FluentValidation/AbstractValidator.cs

http://github.com/JeremySkinner/FluentValidation · C# · 341 lines · 153 code · 47 blank · 141 comment · 6 complexity · e62b3a4e4cc80aed9a40cc2c96a6a4bd MD5 · raw file

  1. #region License
  2. // Copyright (c) .NET Foundation and contributors.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. // The latest version of this file can be found at https://github.com/FluentValidation/FluentValidation
  17. #endregion
  18. namespace FluentValidation {
  19. using System;
  20. using System.Collections;
  21. using System.Collections.Generic;
  22. using System.Linq;
  23. using System.Linq.Expressions;
  24. using System.Threading;
  25. using System.Threading.Tasks;
  26. using Internal;
  27. using Results;
  28. /// <summary>
  29. /// Base class for object validators.
  30. /// </summary>
  31. /// <typeparam name="T">The type of the object being validated</typeparam>
  32. public abstract class AbstractValidator<T> : IValidator<T>, IEnumerable<IValidationRule> {
  33. internal TrackingCollection<IValidationRule> Rules { get; } = new TrackingCollection<IValidationRule>();
  34. private Func<CascadeMode> _cascadeMode = () => ValidatorOptions.CascadeMode;
  35. /// <summary>
  36. /// Sets the cascade mode for all rules within this validator.
  37. /// </summary>
  38. public CascadeMode CascadeMode {
  39. get => _cascadeMode();
  40. set => _cascadeMode = () => value;
  41. }
  42. ValidationResult IValidator.Validate(IValidationContext context) {
  43. context.Guard("Cannot pass null to Validate", nameof(context));
  44. return Validate(ValidationContext<T>.GetFromNonGenericContext(context));
  45. }
  46. Task<ValidationResult> IValidator.ValidateAsync(IValidationContext context, CancellationToken cancellation) {
  47. context.Guard("Cannot pass null to Validate", nameof(context));
  48. return ValidateAsync(ValidationContext<T>.GetFromNonGenericContext(context), cancellation);
  49. }
  50. /// <summary>
  51. /// Validates the specified instance
  52. /// </summary>
  53. /// <param name="instance">The object to validate</param>
  54. /// <returns>A ValidationResult object containing any validation failures</returns>
  55. public ValidationResult Validate(T instance) {
  56. return Validate(new ValidationContext<T>(instance, new PropertyChain(), ValidatorOptions.ValidatorSelectors.DefaultValidatorSelectorFactory()));
  57. }
  58. /// <summary>
  59. /// Validates the specified instance asynchronously
  60. /// </summary>
  61. /// <param name="instance">The object to validate</param>
  62. /// <param name="cancellation">Cancellation token</param>
  63. /// <returns>A ValidationResult object containing any validation failures</returns>
  64. public Task<ValidationResult> ValidateAsync(T instance, CancellationToken cancellation = new CancellationToken()) {
  65. return ValidateAsync(new ValidationContext<T>(instance, new PropertyChain(), ValidatorOptions.ValidatorSelectors.DefaultValidatorSelectorFactory()), cancellation);
  66. }
  67. /// <summary>
  68. /// Validates the specified instance.
  69. /// </summary>
  70. /// <param name="context">Validation Context</param>
  71. /// <returns>A ValidationResult object containing any validation failures.</returns>
  72. public virtual ValidationResult Validate(ValidationContext<T> context) {
  73. context.Guard("Cannot pass null to Validate.", nameof(context));
  74. var result = new ValidationResult();
  75. bool shouldContinue = PreValidate(context, result);
  76. if (!shouldContinue) {
  77. return result;
  78. }
  79. EnsureInstanceNotNull(context.InstanceToValidate);
  80. var failures = Rules.SelectMany(x => x.Validate(context));
  81. foreach (var validationFailure in failures.Where(failure => failure != null)) {
  82. result.Errors.Add(validationFailure);
  83. }
  84. SetExecutedRulesets(result, context);
  85. return result;
  86. }
  87. /// <summary>
  88. /// Validates the specified instance asynchronously.
  89. /// </summary>
  90. /// <param name="context">Validation Context</param>
  91. /// <param name="cancellation">Cancellation token</param>
  92. /// <returns>A ValidationResult object containing any validation failures.</returns>
  93. public async virtual Task<ValidationResult> ValidateAsync(ValidationContext<T> context, CancellationToken cancellation = new CancellationToken()) {
  94. context.Guard("Cannot pass null to Validate", nameof(context));
  95. context.RootContextData["__FV_IsAsyncExecution"] = true;
  96. var result = new ValidationResult();
  97. bool shouldContinue = PreValidate(context, result);
  98. if (!shouldContinue) {
  99. return result;
  100. }
  101. EnsureInstanceNotNull(context.InstanceToValidate);
  102. foreach (var rule in Rules) {
  103. cancellation.ThrowIfCancellationRequested();
  104. var failures = await rule.ValidateAsync(context, cancellation);
  105. foreach (var failure in failures.Where(f => f != null)) {
  106. result.Errors.Add(failure);
  107. }
  108. }
  109. SetExecutedRulesets(result, context);
  110. return result;
  111. }
  112. private void SetExecutedRulesets(ValidationResult result, ValidationContext<T> context) {
  113. var executed = context.RootContextData.GetOrAdd("_FV_RuleSetsExecuted", () => new HashSet<string>{"default"});
  114. result.RuleSetsExecuted = executed.ToArray();
  115. }
  116. /// <summary>
  117. /// Adds a rule to the current validator.
  118. /// </summary>
  119. /// <param name="rule"></param>
  120. protected void AddRule(IValidationRule rule) {
  121. Rules.Add(rule);
  122. }
  123. /// <summary>
  124. /// Creates a <see cref="IValidatorDescriptor" /> that can be used to obtain metadata about the current validator.
  125. /// </summary>
  126. public virtual IValidatorDescriptor CreateDescriptor() {
  127. return new ValidatorDescriptor<T>(Rules);
  128. }
  129. bool IValidator.CanValidateInstancesOfType(Type type) {
  130. if (type == null) throw new ArgumentNullException(nameof(type));
  131. return typeof(T).IsAssignableFrom(type);
  132. }
  133. /// <summary>
  134. /// Defines a validation rule for a specify property.
  135. /// </summary>
  136. /// <example>
  137. /// RuleFor(x => x.Surname)...
  138. /// </example>
  139. /// <typeparam name="TProperty">The type of property being validated</typeparam>
  140. /// <param name="expression">The expression representing the property to validate</param>
  141. /// <returns>an IRuleBuilder instance on which validators can be defined</returns>
  142. public IRuleBuilderInitial<T, TProperty> RuleFor<TProperty>(Expression<Func<T, TProperty>> expression) {
  143. expression.Guard("Cannot pass null to RuleFor", nameof(expression));
  144. // If rule-level caching is enabled, then bypass the expression-level cache.
  145. // Otherwise we essentially end up caching expressions twice unnecessarily.
  146. var rule = PropertyRule.Create(expression, () => CascadeMode);
  147. AddRule(rule);
  148. var ruleBuilder = new RuleBuilder<T, TProperty>(rule, this);
  149. return ruleBuilder;
  150. }
  151. /// <summary>
  152. /// Invokes a rule for each item in the collection
  153. /// </summary>
  154. /// <typeparam name="TElement">Type of property</typeparam>
  155. /// <param name="expression">Expression representing the collection to validate</param>
  156. /// <returns>An IRuleBuilder instance on which validators can be defined</returns>
  157. public IRuleBuilderInitialCollection<T, TElement> RuleForEach<TElement>(Expression<Func<T, IEnumerable<TElement>>> expression) {
  158. expression.Guard("Cannot pass null to RuleForEach", nameof(expression));
  159. var rule = CollectionPropertyRule<T, TElement>.Create(expression, () => CascadeMode);
  160. AddRule(rule);
  161. var ruleBuilder = new RuleBuilder<T, TElement>(rule, this);
  162. return ruleBuilder;
  163. }
  164. /// <summary>
  165. /// Defines a RuleSet that can be used to group together several validators.
  166. /// </summary>
  167. /// <param name="ruleSetName">The name of the ruleset.</param>
  168. /// <param name="action">Action that encapsulates the rules in the ruleset.</param>
  169. public void RuleSet(string ruleSetName, Action action) {
  170. ruleSetName.Guard("A name must be specified when calling RuleSet.", nameof(ruleSetName));
  171. action.Guard("A ruleset definition must be specified when calling RuleSet.", nameof(action));
  172. var ruleSetNames = ruleSetName.Split(',', ';')
  173. .Select(x => x.Trim())
  174. .ToArray();
  175. using (Rules.OnItemAdded(r => r.RuleSets = ruleSetNames)) {
  176. action();
  177. }
  178. }
  179. /// <summary>
  180. /// Defines a condition that applies to several rules
  181. /// </summary>
  182. /// <param name="predicate">The condition that should apply to multiple rules</param>
  183. /// <param name="action">Action that encapsulates the rules.</param>
  184. /// <returns></returns>
  185. public IConditionBuilder When(Func<T, bool> predicate, Action action) {
  186. return When((x, ctx) => predicate(x), action);
  187. }
  188. /// <summary>
  189. /// Defines a condition that applies to several rules
  190. /// </summary>
  191. /// <param name="predicate">The condition that should apply to multiple rules</param>
  192. /// <param name="action">Action that encapsulates the rules.</param>
  193. /// <returns></returns>
  194. public IConditionBuilder When(Func<T, ValidationContext<T>, bool> predicate, Action action) {
  195. return new ConditionBuilder<T>(Rules).When(predicate, action);
  196. }
  197. /// <summary>
  198. /// Defines an inverse condition that applies to several rules
  199. /// </summary>
  200. /// <param name="predicate">The condition that should be applied to multiple rules</param>
  201. /// <param name="action">Action that encapsulates the rules</param>
  202. public IConditionBuilder Unless(Func<T, bool> predicate, Action action) {
  203. return Unless((x, ctx) => predicate(x), action);
  204. }
  205. /// <summary>
  206. /// Defines an inverse condition that applies to several rules
  207. /// </summary>
  208. /// <param name="predicate">The condition that should be applied to multiple rules</param>
  209. /// <param name="action">Action that encapsulates the rules</param>
  210. public IConditionBuilder Unless(Func<T, ValidationContext<T>, bool> predicate, Action action) {
  211. return new ConditionBuilder<T>(Rules).Unless(predicate, action);
  212. }
  213. /// <summary>
  214. /// Defines an asynchronous condition that applies to several rules
  215. /// </summary>
  216. /// <param name="predicate">The asynchronous condition that should apply to multiple rules</param>
  217. /// <param name="action">Action that encapsulates the rules.</param>
  218. /// <returns></returns>
  219. public IConditionBuilder WhenAsync(Func<T, CancellationToken, Task<bool>> predicate, Action action) {
  220. return WhenAsync((x, ctx, cancel) => predicate(x, cancel), action);
  221. }
  222. /// <summary>
  223. /// Defines an asynchronous condition that applies to several rules
  224. /// </summary>
  225. /// <param name="predicate">The asynchronous condition that should apply to multiple rules</param>
  226. /// <param name="action">Action that encapsulates the rules.</param>
  227. /// <returns></returns>
  228. public IConditionBuilder WhenAsync(Func<T, ValidationContext<T>, CancellationToken, Task<bool>> predicate, Action action) {
  229. return new AsyncConditionBuilder<T>(Rules).WhenAsync(predicate, action);
  230. }
  231. /// <summary>
  232. /// Defines an inverse asynchronous condition that applies to several rules
  233. /// </summary>
  234. /// <param name="predicate">The asynchronous condition that should be applied to multiple rules</param>
  235. /// <param name="action">Action that encapsulates the rules</param>
  236. public IConditionBuilder UnlessAsync(Func<T, CancellationToken, Task<bool>> predicate, Action action) {
  237. return UnlessAsync((x, ctx, cancel) => predicate(x, cancel), action);
  238. }
  239. /// <summary>
  240. /// Defines an inverse asynchronous condition that applies to several rules
  241. /// </summary>
  242. /// <param name="predicate">The asynchronous condition that should be applied to multiple rules</param>
  243. /// <param name="action">Action that encapsulates the rules</param>
  244. public IConditionBuilder UnlessAsync(Func<T, ValidationContext<T>, CancellationToken, Task<bool>> predicate, Action action) {
  245. return new AsyncConditionBuilder<T>(Rules).UnlessAsync(predicate, action);
  246. }
  247. /// <summary>
  248. /// Includes the rules from the specified validator
  249. /// </summary>
  250. public void Include(IValidator<T> rulesToInclude) {
  251. rulesToInclude.Guard("Cannot pass null to Include", nameof(rulesToInclude));
  252. var rule = IncludeRule<T>.Create(rulesToInclude, () => CascadeMode);
  253. AddRule(rule);
  254. }
  255. /// <summary>
  256. /// Includes the rules from the specified validator
  257. /// </summary>
  258. public void Include<TValidator>(Func<T, TValidator> rulesToInclude) where TValidator : IValidator<T> {
  259. rulesToInclude.Guard("Cannot pass null to Include", nameof(rulesToInclude));
  260. var rule = IncludeRule<T>.Create(rulesToInclude, () => CascadeMode);
  261. AddRule(rule);
  262. }
  263. /// <summary>
  264. /// Returns an enumerator that iterates through the collection of validation rules.
  265. /// </summary>
  266. /// <returns>
  267. /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
  268. /// </returns>
  269. /// <filterpriority>1</filterpriority>
  270. public IEnumerator<IValidationRule> GetEnumerator() {
  271. return Rules.GetEnumerator();
  272. }
  273. IEnumerator IEnumerable.GetEnumerator() {
  274. return GetEnumerator();
  275. }
  276. /// <summary>
  277. /// Throws an exception if the instance being validated is null.
  278. /// </summary>
  279. /// <param name="instanceToValidate"></param>
  280. protected virtual void EnsureInstanceNotNull(object instanceToValidate) {
  281. instanceToValidate.Guard("Cannot pass null model to Validate.", nameof(instanceToValidate));
  282. }
  283. /// <summary>
  284. /// Determines if validation should occur and provides a means to modify the context and ValidationResult prior to execution.
  285. /// If this method returns false, then the ValidationResult is immediately returned from Validate/ValidateAsync.
  286. /// </summary>
  287. /// <param name="context"></param>
  288. /// <param name="result"></param>
  289. /// <returns></returns>
  290. protected virtual bool PreValidate(ValidationContext<T> context, ValidationResult result) {
  291. return true;
  292. }
  293. }
  294. }