e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
626 lines
28 KiB
C#
626 lines
28 KiB
C#
//-----------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace System.Activities.Validation
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Runtime;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Linq;
|
|
|
|
public static class ActivityValidationServices
|
|
{
|
|
internal static readonly ReadOnlyCollection<Activity> EmptyChildren = new ReadOnlyCollection<Activity>(new Activity[0]);
|
|
static ValidationSettings defaultSettings = new ValidationSettings();
|
|
internal static ReadOnlyCollection<ValidationError> EmptyValidationErrors = new ReadOnlyCollection<ValidationError>(new List<ValidationError>(0));
|
|
|
|
public static ValidationResults Validate(Activity toValidate)
|
|
{
|
|
return Validate(toValidate, defaultSettings);
|
|
}
|
|
|
|
public static ValidationResults Validate(Activity toValidate, ValidationSettings settings)
|
|
{
|
|
if (toValidate == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("toValidate");
|
|
}
|
|
|
|
if (settings == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("settings");
|
|
}
|
|
|
|
if (toValidate.HasBeenAssociatedWithAnInstance)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.RootActivityAlreadyAssociatedWithInstance(toValidate.DisplayName)));
|
|
}
|
|
|
|
if (settings.PrepareForRuntime && (settings.SingleLevel || settings.SkipValidatingRootConfiguration || settings.OnlyUseAdditionalConstraints))
|
|
{
|
|
throw FxTrace.Exception.Argument("settings", SR.InvalidPrepareForRuntimeValidationSettings);
|
|
}
|
|
|
|
InternalActivityValidationServices validator = new InternalActivityValidationServices(settings, toValidate);
|
|
return validator.InternalValidate();
|
|
}
|
|
|
|
public static Activity Resolve(Activity root, string id)
|
|
{
|
|
return WorkflowInspectionServices.Resolve(root, id);
|
|
}
|
|
|
|
internal static void ThrowIfViolationsExist(IList<ValidationError> validationErrors, ExceptionReason reason = ExceptionReason.InvalidTree)
|
|
{
|
|
Exception exception = CreateExceptionFromValidationErrors(validationErrors, reason);
|
|
|
|
if (exception != null)
|
|
{
|
|
throw FxTrace.Exception.AsError(exception);
|
|
}
|
|
}
|
|
|
|
static Exception CreateExceptionFromValidationErrors(IList<ValidationError> validationErrors, ExceptionReason reason)
|
|
{
|
|
if (validationErrors != null && validationErrors.Count > 0)
|
|
{
|
|
string exceptionString = GenerateExceptionString(validationErrors, reason);
|
|
|
|
if (exceptionString != null)
|
|
{
|
|
return new InvalidWorkflowException(exceptionString);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
internal static List<Activity> GetChildren(ActivityUtilities.ChildActivity root, ActivityUtilities.ActivityCallStack parentChain, ProcessActivityTreeOptions options)
|
|
{
|
|
ActivityUtilities.FinishCachingSubtree(root, parentChain, options);
|
|
|
|
List<Activity> listOfChildren = new List<Activity>();
|
|
|
|
foreach (Activity activity in WorkflowInspectionServices.GetActivities(root.Activity))
|
|
{
|
|
listOfChildren.Add(activity);
|
|
}
|
|
|
|
int toProcessIndex = 0;
|
|
|
|
while (toProcessIndex < listOfChildren.Count)
|
|
{
|
|
foreach (Activity activity in WorkflowInspectionServices.GetActivities(listOfChildren[toProcessIndex]))
|
|
{
|
|
listOfChildren.Add(activity);
|
|
}
|
|
|
|
toProcessIndex++;
|
|
}
|
|
|
|
return listOfChildren;
|
|
}
|
|
|
|
internal static void ValidateRootInputs(Activity rootActivity, IDictionary<string, object> inputs)
|
|
{
|
|
IList<ValidationError> validationErrors = null;
|
|
ValidationHelper.ValidateArguments(rootActivity, rootActivity.EquivalenceInfo, rootActivity.OverloadGroups, rootActivity.RequiredArgumentsNotInOverloadGroups, inputs, ref validationErrors);
|
|
|
|
// Validate if there are any extra arguments passed in the input dictionary
|
|
if (inputs != null)
|
|
{
|
|
List<string> unusedArguments = null;
|
|
IEnumerable<RuntimeArgument> arguments = rootActivity.RuntimeArguments.Where((a) => ArgumentDirectionHelper.IsIn(a.Direction));
|
|
|
|
foreach (string key in inputs.Keys)
|
|
{
|
|
bool found = false;
|
|
foreach (RuntimeArgument argument in arguments)
|
|
{
|
|
if (argument.Name == key)
|
|
{
|
|
found = true;
|
|
|
|
// Validate if the input argument type matches the expected argument type.
|
|
object inputArgumentValue = null;
|
|
if (inputs.TryGetValue(key, out inputArgumentValue))
|
|
{
|
|
if (!TypeHelper.AreTypesCompatible(inputArgumentValue, argument.Type))
|
|
{
|
|
ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.InputParametersTypeMismatch(argument.Type, argument.Name), rootActivity));
|
|
}
|
|
}
|
|
// The ValidateArguments will validate Required in-args and hence not duplicating that validation if the key is not found.
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
if (unusedArguments == null)
|
|
{
|
|
unusedArguments = new List<string>();
|
|
}
|
|
unusedArguments.Add(key);
|
|
}
|
|
}
|
|
if (unusedArguments != null)
|
|
{
|
|
ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.UnusedInputArguments(unusedArguments.AsCommaSeparatedValues()), rootActivity));
|
|
}
|
|
}
|
|
|
|
if (validationErrors != null && validationErrors.Count > 0)
|
|
{
|
|
string parameterName = "rootArgumentValues";
|
|
ExceptionReason reason = ExceptionReason.InvalidNonNullInputs;
|
|
|
|
if (inputs == null)
|
|
{
|
|
parameterName = "program";
|
|
reason = ExceptionReason.InvalidNullInputs;
|
|
}
|
|
|
|
string exceptionString = GenerateExceptionString(validationErrors, reason);
|
|
|
|
if (exceptionString != null)
|
|
{
|
|
throw FxTrace.Exception.Argument(parameterName, exceptionString);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void ValidateArguments(Activity activity, bool isRoot, ref IList<ValidationError> validationErrors)
|
|
{
|
|
Fx.Assert(activity != null, "Activity to validate should not be null.");
|
|
|
|
Dictionary<string, List<RuntimeArgument>> overloadGroups;
|
|
List<RuntimeArgument> requiredArgumentsNotInOverloadGroups;
|
|
ValidationHelper.OverloadGroupEquivalenceInfo equivalenceInfo;
|
|
if (ValidationHelper.GatherAndValidateOverloads(activity, out overloadGroups, out requiredArgumentsNotInOverloadGroups, out equivalenceInfo, ref validationErrors))
|
|
{
|
|
// If we're not the root and the overload groups are valid
|
|
// then we validate the arguments
|
|
if (!isRoot)
|
|
{
|
|
ValidationHelper.ValidateArguments(activity, equivalenceInfo, overloadGroups, requiredArgumentsNotInOverloadGroups, null, ref validationErrors);
|
|
}
|
|
}
|
|
|
|
// If we are the root, regardless of whether the groups are
|
|
// valid or not, we cache the group information
|
|
if (isRoot)
|
|
{
|
|
activity.OverloadGroups = overloadGroups;
|
|
activity.RequiredArgumentsNotInOverloadGroups = requiredArgumentsNotInOverloadGroups;
|
|
activity.EquivalenceInfo = equivalenceInfo;
|
|
}
|
|
}
|
|
|
|
static string GenerateExceptionString(IList<ValidationError> validationErrors, ExceptionReason reason)
|
|
{
|
|
// 4096 is an arbitrary constant. Currently clipped by character count (not bytes).
|
|
const int maxExceptionStringSize = 4096;
|
|
|
|
StringBuilder exceptionMessageBuilder = null;
|
|
|
|
for (int i = 0; i < validationErrors.Count; i++)
|
|
{
|
|
ValidationError validationError = validationErrors[i];
|
|
|
|
if (!validationError.IsWarning)
|
|
{
|
|
// create the common exception string
|
|
if (exceptionMessageBuilder == null)
|
|
{
|
|
exceptionMessageBuilder = new StringBuilder();
|
|
|
|
switch (reason)
|
|
{
|
|
case ExceptionReason.InvalidTree:
|
|
exceptionMessageBuilder.Append(SR.ErrorsEncounteredWhileProcessingTree);
|
|
break;
|
|
case ExceptionReason.InvalidNonNullInputs:
|
|
exceptionMessageBuilder.Append(SR.RootArgumentViolationsFound);
|
|
break;
|
|
case ExceptionReason.InvalidNullInputs:
|
|
exceptionMessageBuilder.Append(SR.RootArgumentViolationsFoundNoInputs);
|
|
break;
|
|
}
|
|
}
|
|
|
|
string activityName = null;
|
|
|
|
if (validationError.Source != null)
|
|
{
|
|
activityName = validationError.Source.DisplayName;
|
|
}
|
|
else
|
|
{
|
|
activityName = "<UnknownActivity>";
|
|
}
|
|
|
|
exceptionMessageBuilder.AppendLine();
|
|
exceptionMessageBuilder.Append(string.Format(SR.Culture, "'{0}': {1}", activityName, validationError.Message));
|
|
|
|
if (exceptionMessageBuilder.Length > maxExceptionStringSize)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
string exceptionString = null;
|
|
|
|
if (exceptionMessageBuilder != null)
|
|
{
|
|
exceptionString = exceptionMessageBuilder.ToString();
|
|
|
|
if (exceptionString.Length > maxExceptionStringSize)
|
|
{
|
|
string snipNotification = SR.TooManyViolationsForExceptionMessage;
|
|
|
|
exceptionString = exceptionString.Substring(0, maxExceptionStringSize - snipNotification.Length);
|
|
exceptionString += snipNotification;
|
|
}
|
|
}
|
|
|
|
return exceptionString;
|
|
}
|
|
|
|
static internal string GenerateValidationErrorPrefix(Activity toValidate, ActivityUtilities.ActivityCallStack parentChain, ProcessActivityTreeOptions options, out Activity source)
|
|
{
|
|
bool parentVisible = true;
|
|
string prefix = "";
|
|
source = toValidate;
|
|
|
|
// Processing for implementation of activity
|
|
// during build time
|
|
if (options.SkipRootConfigurationValidation)
|
|
{
|
|
// Check if the activity is a implementation child
|
|
if (toValidate.MemberOf.Parent != null)
|
|
{
|
|
// Check if activity is an immediate implementation child
|
|
// of x:class activity. This means that the activity is
|
|
// being designed and hence we do not want to add the
|
|
// prefix at build time
|
|
if (toValidate.MemberOf.Parent.Parent == null)
|
|
{
|
|
prefix = "";
|
|
source = toValidate;
|
|
}
|
|
else
|
|
{
|
|
// This means the activity is a child of immediate implementation child
|
|
// of x:class activity which means the activity is not visible.
|
|
// The source points to the first visible parent activity in the
|
|
// parent chain.
|
|
while (source.MemberOf.Parent.Parent != null)
|
|
{
|
|
source = source.Parent;
|
|
}
|
|
prefix = SR.ValidationErrorPrefixForHiddenActivity(source);
|
|
}
|
|
return prefix;
|
|
}
|
|
}
|
|
|
|
// Find out if any of the parents of the activity are not publicly visible
|
|
for (int i = 0; i < parentChain.Count; i++)
|
|
{
|
|
if (parentChain[i].Activity.MemberOf.Parent != null)
|
|
{
|
|
parentVisible = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Figure out the source of validation error:
|
|
// - For hidden activity - source will be closest visible public parent
|
|
// - For visible activity - source will be the activity itself
|
|
// In current design an activity is visible only if it is in the root id space.
|
|
// In future, if we provide a knob for the user to specify the
|
|
// id spaces that are visible, then this check needs to be changed
|
|
// to iterate over the parentChain and find the closest parent activity that
|
|
// is in the visible id spaces.
|
|
while (source.MemberOf.Parent != null)
|
|
{
|
|
source = source.Parent;
|
|
}
|
|
|
|
if (toValidate.MemberOf.Parent != null)
|
|
{
|
|
// Activity itself is hidden
|
|
prefix = SR.ValidationErrorPrefixForHiddenActivity(source);
|
|
}
|
|
else
|
|
{
|
|
if (!parentVisible)
|
|
{
|
|
// Activity itself is public but has a private parent
|
|
prefix = SR.ValidationErrorPrefixForPublicActivityWithHiddenParent(source.Parent, source);
|
|
}
|
|
}
|
|
return prefix;
|
|
}
|
|
|
|
internal static void RunConstraints(ActivityUtilities.ChildActivity childActivity, ActivityUtilities.ActivityCallStack parentChain, IList<Constraint> constraints, ProcessActivityTreeOptions options, bool suppressGetChildrenViolations, ref IList<ValidationError> validationErrors)
|
|
{
|
|
if (constraints != null)
|
|
{
|
|
Activity toValidate = childActivity.Activity;
|
|
|
|
LocationReferenceEnvironment environment = toValidate.GetParentEnvironment();
|
|
|
|
Dictionary<string, object> inputDictionary = new Dictionary<string, object>(2);
|
|
|
|
for (int constraintIndex = 0; constraintIndex < constraints.Count; constraintIndex++)
|
|
{
|
|
Constraint constraint = constraints[constraintIndex];
|
|
|
|
// there may be null entries here
|
|
if (constraint == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
inputDictionary[Constraint.ToValidateArgumentName] = toValidate;
|
|
ValidationContext validationContext = new ValidationContext(childActivity, parentChain, options, environment);
|
|
inputDictionary[Constraint.ToValidateContextArgumentName] = validationContext;
|
|
IDictionary<string, object> results = null;
|
|
|
|
try
|
|
{
|
|
results = WorkflowInvoker.Invoke(constraint, inputDictionary);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
ValidationError constraintExceptionValidationError = new ValidationError(SR.InternalConstraintException(constraint.DisplayName, toValidate.GetType().FullName, toValidate.DisplayName, e.ToString()), false)
|
|
{
|
|
Source = toValidate,
|
|
Id = toValidate.Id
|
|
};
|
|
|
|
ActivityUtilities.Add(ref validationErrors, constraintExceptionValidationError);
|
|
}
|
|
|
|
if (results != null)
|
|
{
|
|
object resultValidationErrors;
|
|
if (results.TryGetValue(Constraint.ValidationErrorListArgumentName, out resultValidationErrors))
|
|
{
|
|
IList<ValidationError> validationErrorList = (IList<ValidationError>)resultValidationErrors;
|
|
|
|
if (validationErrorList.Count > 0)
|
|
{
|
|
if (validationErrors == null)
|
|
{
|
|
validationErrors = new List<ValidationError>();
|
|
}
|
|
|
|
Activity source;
|
|
string prefix = ActivityValidationServices.GenerateValidationErrorPrefix(childActivity.Activity, parentChain, options, out source);
|
|
|
|
for (int validationErrorIndex = 0; validationErrorIndex < validationErrorList.Count; validationErrorIndex++)
|
|
{
|
|
ValidationError validationError = validationErrorList[validationErrorIndex];
|
|
|
|
validationError.Source = source;
|
|
validationError.Id = source.Id;
|
|
if (!string.IsNullOrEmpty(prefix))
|
|
{
|
|
validationError.Message = prefix + validationError.Message;
|
|
}
|
|
validationErrors.Add(validationError);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!suppressGetChildrenViolations)
|
|
{
|
|
validationContext.AddGetChildrenErrors(ref validationErrors);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static bool HasErrors(IList<ValidationError> validationErrors)
|
|
{
|
|
if (validationErrors != null && validationErrors.Count > 0)
|
|
{
|
|
for (int i = 0; i < validationErrors.Count; i++)
|
|
{
|
|
if (!validationErrors[i].IsWarning)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
class InternalActivityValidationServices
|
|
{
|
|
ValidationSettings settings;
|
|
Activity rootToValidate;
|
|
IList<ValidationError> errors;
|
|
ProcessActivityTreeOptions options;
|
|
Activity expressionRoot;
|
|
LocationReferenceEnvironment environment;
|
|
|
|
internal InternalActivityValidationServices(ValidationSettings settings, Activity toValidate)
|
|
{
|
|
this.settings = settings;
|
|
this.rootToValidate = toValidate;
|
|
this.environment = settings.Environment;
|
|
}
|
|
|
|
internal ValidationResults InternalValidate()
|
|
{
|
|
this.options = ProcessActivityTreeOptions.GetValidationOptions(this.settings);
|
|
|
|
if (this.settings.OnlyUseAdditionalConstraints)
|
|
{
|
|
// We don't want the errors from CacheMetadata so we send those to a "dummy" list.
|
|
IList<ValidationError> suppressedErrors = null;
|
|
ActivityUtilities.CacheRootMetadata(this.rootToValidate, this.environment, this.options, new ActivityUtilities.ProcessActivityCallback(ValidateElement), ref suppressedErrors);
|
|
}
|
|
else
|
|
{
|
|
// We want to add the CacheMetadata errors to our errors collection
|
|
ActivityUtilities.CacheRootMetadata(this.rootToValidate, this.environment, this.options, new ActivityUtilities.ProcessActivityCallback(ValidateElement), ref this.errors);
|
|
}
|
|
|
|
return new ValidationResults(this.errors);
|
|
}
|
|
|
|
void ValidateElement(ActivityUtilities.ChildActivity childActivity, ActivityUtilities.ActivityCallStack parentChain)
|
|
{
|
|
Activity toValidate = childActivity.Activity;
|
|
|
|
if (!this.settings.SingleLevel || object.ReferenceEquals(toValidate, this.rootToValidate))
|
|
{
|
|
// 0. Open time violations are captured by the CacheMetadata walk.
|
|
|
|
// 1. Argument validations are done by the CacheMetadata walk.
|
|
|
|
// 2. Build constraints are done by the CacheMetadata walk.
|
|
|
|
// 3. Then do policy constraints
|
|
if (this.settings.HasAdditionalConstraints && childActivity.CanBeExecuted && parentChain.WillExecute)
|
|
{
|
|
bool suppressGetChildrenViolations = this.settings.OnlyUseAdditionalConstraints || this.settings.SingleLevel;
|
|
|
|
Type currentType = toValidate.GetType();
|
|
|
|
while (currentType != null)
|
|
{
|
|
IList<Constraint> policyConstraints;
|
|
if (this.settings.AdditionalConstraints.TryGetValue(currentType, out policyConstraints))
|
|
{
|
|
RunConstraints(childActivity, parentChain, policyConstraints, this.options, suppressGetChildrenViolations, ref this.errors);
|
|
}
|
|
|
|
if (currentType.IsGenericType)
|
|
{
|
|
Type genericDefinitionType = currentType.GetGenericTypeDefinition();
|
|
if (genericDefinitionType != null)
|
|
{
|
|
IList<Constraint> genericTypePolicyConstraints;
|
|
if (this.settings.AdditionalConstraints.TryGetValue(genericDefinitionType, out genericTypePolicyConstraints))
|
|
{
|
|
RunConstraints(childActivity, parentChain, genericTypePolicyConstraints, this.options, suppressGetChildrenViolations, ref this.errors);
|
|
}
|
|
}
|
|
}
|
|
currentType = currentType.BaseType;
|
|
}
|
|
}
|
|
|
|
//4. Validate if the argument expression subtree contains an activity that can induce idle.
|
|
if (childActivity.Activity.IsExpressionRoot)
|
|
{
|
|
if (childActivity.Activity.HasNonEmptySubtree)
|
|
{
|
|
this.expressionRoot = childActivity.Activity;
|
|
// Back-compat: In Dev10 we always used ProcessActivityTreeOptions.FullCachingOptions here, and ignored this.options.
|
|
// So we need to continue to do that, unless the new Dev11 flag SkipRootConfigurationValidation is passed.
|
|
ProcessActivityTreeOptions options = this.options.SkipRootConfigurationValidation ? this.options : ProcessActivityTreeOptions.FullCachingOptions;
|
|
ActivityUtilities.FinishCachingSubtree(childActivity, parentChain, options, ValidateExpressionSubtree);
|
|
this.expressionRoot = null;
|
|
}
|
|
else if (childActivity.Activity.InternalCanInduceIdle)
|
|
{
|
|
Activity activity = childActivity.Activity;
|
|
RuntimeArgument runtimeArgument = GetBoundRuntimeArgument(activity);
|
|
ValidationError error = new ValidationError(SR.CanInduceIdleActivityInArgumentExpression(runtimeArgument.Name, activity.Parent.DisplayName, activity.DisplayName), true, runtimeArgument.Name, activity.Parent);
|
|
ActivityUtilities.Add(ref this.errors, error);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void ValidateExpressionSubtree(ActivityUtilities.ChildActivity childActivity, ActivityUtilities.ActivityCallStack parentChain)
|
|
{
|
|
Fx.Assert(this.expressionRoot != null, "This callback should be called activities in the expression subtree only.");
|
|
|
|
if (childActivity.Activity.InternalCanInduceIdle)
|
|
{
|
|
Activity activity = childActivity.Activity;
|
|
Activity expressionRoot = this.expressionRoot;
|
|
|
|
RuntimeArgument runtimeArgument = GetBoundRuntimeArgument(expressionRoot);
|
|
ValidationError error = new ValidationError(SR.CanInduceIdleActivityInArgumentExpression(runtimeArgument.Name, expressionRoot.Parent.DisplayName, activity.DisplayName), true, runtimeArgument.Name, expressionRoot.Parent);
|
|
ActivityUtilities.Add(ref this.errors, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Iterate through all runtime arguments on the configured activity
|
|
// and find the one that binds to expressionActivity.
|
|
static RuntimeArgument GetBoundRuntimeArgument(Activity expressionActivity)
|
|
{
|
|
Activity configuredActivity = expressionActivity.Parent;
|
|
Fx.Assert(configuredActivity != null, "Configured activity should not be null.");
|
|
|
|
RuntimeArgument boundRuntimeArgument = null;
|
|
for (int i = 0; i < configuredActivity.RuntimeArguments.Count; i++)
|
|
{
|
|
boundRuntimeArgument = configuredActivity.RuntimeArguments[i];
|
|
if (object.ReferenceEquals(boundRuntimeArgument.BoundArgument.Expression, expressionActivity))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
Fx.Assert(boundRuntimeArgument != null, "We should always be able to find the runtime argument!");
|
|
return boundRuntimeArgument;
|
|
}
|
|
|
|
// This method checks for duplicate evaluation order entries in a collection that is
|
|
// sorted in ascendng order of evaluation order values.
|
|
internal static void ValidateEvaluationOrder(IList<RuntimeArgument> runtimeArguments, Activity referenceActivity, ref IList<ValidationError> validationErrors)
|
|
{
|
|
for (int i = 0; i < runtimeArguments.Count - 1; i++)
|
|
{
|
|
RuntimeArgument argument = runtimeArguments[i];
|
|
RuntimeArgument nextArgument = runtimeArguments[i + 1];
|
|
if (argument.IsEvaluationOrderSpecified && nextArgument.IsEvaluationOrderSpecified)
|
|
{
|
|
if (argument.BoundArgument.EvaluationOrder == nextArgument.BoundArgument.EvaluationOrder)
|
|
{
|
|
ActivityUtilities.Add(ref validationErrors, new ValidationError(SR.DuplicateEvaluationOrderValues(referenceActivity.DisplayName, argument.BoundArgument.EvaluationOrder), false, argument.Name, referenceActivity));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal enum ExceptionReason
|
|
{
|
|
InvalidTree,
|
|
InvalidNullInputs,
|
|
InvalidNonNullInputs,
|
|
}
|
|
|
|
}
|
|
}
|