/// <summary> /// Starts a validation asynchronously and returns a Task for the operation. /// </summary> /// <typeparam name="TTarget"> The type of the object to validate. </typeparam> /// <param name="plan"> The validation plan to execute asynchronously. </param> /// <param name="target"> The type of the object to validate. </param> /// <returns> </returns> public static Task <ValidationReport> ExecuteAsync <TTarget>( this ValidationPlan <TTarget> plan, TTarget target) { var tcs = new TaskCompletionSource <ValidationReport>(target); var scope = new ValidationScope(enter: false); Task.Factory.StartNew(() => { var taskDictionary = new ConcurrentDictionary <IValidationRule <TTarget>, Task <bool> >(); // create and start a task for each rule plan.Select( rule => taskDictionary.GetOrAdd(rule, rule.ToTask(target, taskDictionary, scope, TaskCreationOptions.AttachedToParent))) .ForEach(t => t.TryStart()); }) .ContinueWith(t => { try { tcs.Complete(t, () => ValidationReport.FromScope(scope)); } finally { scope.Dispose(); } }); return(tcs.Task); }
/// <summary> /// Determines whether the specified target is valid. /// </summary> /// <param name="target"> The target. </param> /// <returns> <c>true</c> if the specified target is valid; otherwise, <c>false</c> . </returns> public bool Check(TTarget target) { using (var scope = new ValidationScope { Rule = this }) { return(Check(target, scope)); } }
/// <summary> /// Determines (synchronously) whether the specified target is valid. /// </summary> /// <param name="target"> The object to be validated. </param> /// <param name="scope"> The <see cref="ValidationScope" /> in which to perform the validation. </param> /// <returns> <c>true</c> if the specified target is valid; otherwise, <c>false</c> . </returns> public override bool Check(TTarget target, ValidationScope scope) { var task = TaskFor(target, scope).TryStart(); if (task.Status == TaskStatus.WaitingForActivation) { throw new InvalidOperationException("The rule cannot be evaluated because its task is dependent on another task that has not been started."); } return(task.Result); }
/// <summary> /// Performs the actual rule check. /// </summary> protected override bool PerformCheck(TTarget target, ValidationScope scope = null) { using (scope = new ValidationScope(scope) { Rule = this }) { return(scope.HaltsOnFirstFailure ? EvaluationStrategy <TTarget> .HaltOnFirstFailure.Evaluate(this, target, scope) : Strategy.Evaluate(this, target, scope)); } }
/// <summary> /// Exit the specified <paramref name="scope" /> . /// </summary> /// <remarks> /// If there are nested scopes within the specified <paramref name="scope" /> , those will be exited as well. /// </remarks> /// <param name="scope"> The scope to exit </param> protected static void ExitScope(ValidationScope scope) { if (!ActiveScopes.Contains(scope)) { return; } ValidationScope popped; do { popped = ActiveScopes.Pop(); } while (scope != popped); }
/// <summary> /// Determines whether the specified target is valid. /// </summary> /// <param name="target"> The target. </param> /// <param name="scope"> The ValidationScope for the operation. </param> /// <returns> <c>true</c> if the specified target is valid; otherwise, <c>false</c> . </returns> public virtual bool Check(TTarget target, ValidationScope scope) { // when there is no active ValidationScope, simply perform the check if (scope == null) { if (preconditions != null && preconditions.Any(rule => !rule.Check(target))) { // this rule is considered to have succeeded, i.e. not failed, because it has not been evaluated, so return true: return(true); } return(PerformCheck(target, scope)); } if (CheckPreconditions(scope.Parent ?? scope, target)) { return(true); } // get the result of the rule var result = PerformCheck(target, scope); var messageGenerator = MessageGenerator ?? scope.MessageGenerator; // extract paramaters from the scope and store them if needed in a FailedEvaluation var parameters = scope.FlushParameters(); if (!result) { // record the failure in the ValidationScope scope.AddEvaluation(new FailedEvaluation(target, this, messageGenerator) { IsInternal = CreatesEvaluationsAsInternal, Parameters = parameters }); } else { scope.AddEvaluation(new SuccessfulEvaluation(target, this, messageGenerator) { IsInternal = CreatesEvaluationsAsInternal, Parameters = parameters }); } return(result); }
/// <summary> /// Initializes a new instance of the <see cref="ValidationScope" /> class. /// </summary> public ValidationScope(bool enter = true) { if (activeScopes != null && activeScopes.Count > 0) { parent = ActiveScopes.Peek(); evaluations = parent.evaluations; } else { evaluations = new ConcurrentBag <RuleEvaluation>(); } if (enter) { EnterScope(this); } }
private Func <TTarget, ValidationScope, Task <bool> > CreateTask(Func <TTarget, Task <TTarget> > setup, Func <TTarget, bool> validate) => (target, parentScope) => { var tcs = new TaskCompletionSource <bool>(); var task = setup(target); task.ContinueWith( t => tcs.Complete(t, () => { using (var scope = new ValidationScope(parentScope)) { var result = validate(t.Result); RecordResult(scope, result, target); return(result); } })); return(tcs.Task); };
public Task <bool> TaskFor(TTarget target, ValidationScope scope) { var tcs = new TaskCompletionSource <bool>(); Task.Factory .StartNew(() => { // TODO: (TaskFor) prevent setup from running if a precondition failed. IsPreconditionUnsatisfied may not be appropriate for async since it forces evaluation when it finds an unevaluated precondition. // if (CheckPreconditions(scope, target)) // { // tcs.SetCanceled(); // return; // } taskFactory(target, scope).TryStart(); }); return(tcs.Task); }
/// <summary> /// Evaluates all constituent validation rules against the specified target. /// </summary> /// <param name="target"> The target. </param> /// <param name="haltOnFirstFailure"> When true, stops execution on the first failed rule </param> /// <returns> </returns> public ValidationReport Execute(TTarget target, bool haltOnFirstFailure = false) { using (var scope = new ValidationScope { MessageGenerator = MessageGenerator, Rule = this }) { if (haltOnFirstFailure) { scope.HaltsOnFirstFailure = true; } foreach (var rule in rules) { var ruleTemp = rule; if (!scope.HasFailed(target, ruleTemp)) { var success = rule.Check(target); // ValidationRule<TTarget> handles this internally but otherwise add it to the scope. if (!(rule is ValidationRule <TTarget>)) { if (!success) { scope.AddEvaluation(new FailedEvaluation(target, rule, MessageGenerator)); } else { scope.AddEvaluation(new SuccessfulEvaluation(target, rule, MessageGenerator)); } } } if (scope.ShouldHalt()) { break; } } return(ValidationReport.FromScope(scope)); } }
private void RecordResult(ValidationScope scope, bool result, TTarget target) { var parameters = scope.FlushParameters(); var messageGenerator = scope.MessageGenerator; if (result) { scope.AddEvaluation(new SuccessfulEvaluation(target, this) { MessageGenerator = messageGenerator, Parameters = parameters }); } else { scope.AddEvaluation(new FailedEvaluation(target, this) { MessageGenerator = messageGenerator, Parameters = parameters }); } }
private static Task <bool> ToTask <T>( this IValidationRule <T> forRule, T target, ConcurrentDictionary <IValidationRule <T>, Task <bool> > tasks, ValidationScope scope, TaskCreationOptions options = TaskCreationOptions.PreferFairness) { // if the rule is inherently capable of async, let it build the task var asyncRule = forRule as AsyncValidationRule <T>; if (asyncRule != null) { return(asyncRule.TaskFor(target, scope)); } // otherwise, build a task from its tree var task = new Task <bool>( () => { var rule = forRule as ValidationRule <T>; // there are preconditions, so each will need its own task, and main task must wait on them all rule?.preconditions .ForEach(pre => tasks.GetOrAdd(rule, _ => pre.ToTask(target, tasks, scope, TaskCreationOptions.AttachedToParent))); using (var innerScope = new ValidationScope(scope)) { return(forRule.Check(target, innerScope)); } }, options); return(task); }
internal static ValidationReport FromScope(ValidationScope scope) => new ValidationReport(scope.Evaluations);
/// <summary> /// Enters the specified <paramref name="scope" /> . /// </summary> /// <param name="scope"> The scope. </param> protected static void EnterScope(ValidationScope scope) { ActiveScopes.Push(scope); }
/// <summary> /// Initializes a new instance of the <see cref="ValidationScope" /> class. /// </summary> /// <param name="parent"> The parent. </param> public ValidationScope(ValidationScope parent) { this.parent = parent; evaluations = parent.evaluations; EnterScope(this); }
/// <summary> /// Performs the actual rule check. /// </summary> protected virtual bool PerformCheck(TTarget target, ValidationScope scope = null) => condition(target);
protected bool CheckPreconditions(ValidationScope scope, TTarget target) { if (preconditions == null) { return(false); } // check previously-evaluated preconditions. any occurrence of the same combination of rule and target will short circuit the current operation. if (scope.AllFailures.Any(f => preconditions.Any(pre => Equals(f.Target, target) && ValidationRuleComparer.Instance.Equals(pre, f.Rule)))) { // this failure indicates a short-circuited precondition. this mechanism identifies preconditions of preconditions. // TODO: (CheckPreconditions) mark the failure so that it can be identified as such for debug purposes scope.AddEvaluation(new FailedEvaluation(target, this) { IsInternal = true }); return(true); } // check unevaluated preconditions foreach (var rule in preconditions) { var tempRule = rule; if (!scope.Evaluations.Any(ex => Equals(ex.Target, target) && ValidationRuleComparer.Instance.Equals(ex.Rule, tempRule))) { using (var internalScope = new ValidationScope { Rule = this }) { internalScope.RuleEvaluated += (s, e) => { var failure = e.RuleEvaluation as FailedEvaluation; if (failure != null) { failure.IsInternal = true; } }; if (!rule.Check(target)) { return(true); } } // finally we have to check if the rule's preconditions were in turn unsatisfied. var vRule = rule as ValidationRule <TTarget>; if (vRule != null) { if (vRule.CheckPreconditions(scope, target)) { return(true); } } } } return(false); }
public virtual bool Evaluate(IEnumerable <IValidationRule <T> > rules, T target, ValidationScope scope) => evaluate(rules, target, scope);