internal static void UpdateInvalidArgumentsIfNecessary(object sender, ValidationService.ErrorsMarkedEventArgs args) { if (args.Reason != ValidationReason.ModelChange) { return; } if (args.Errors.Count == 0) { return; } using (EditingScope editingScope = args.ModelTreeManager.CreateEditingScope(string.Empty)) { // Prevent the validation -> fix arguments -> validation loop. editingScope.SuppressUndo = true; // Suppress validation. We will do it ourselves (see below) editingScope.SuppressValidationOnComplete = true; // Re-compile erroreous expressions to see if update is necessary ValidationService validationService = args.Context.Services.GetRequiredService<ValidationService>(); ArgumentAccessorWrapperCache argumentAccessorWrapperCache = new ArgumentAccessorWrapperCache(); List<ExpressionReplacement> expressionReplacements = ComputeExpressionReplacements(args.Errors.Select(error => error.Source).OfType<ActivityWithResult>(), args.Context, argumentAccessorWrapperCache); bool argumentReplacementOccurred = false; if (expressionReplacements.Count > 0) { try { foreach (ExpressionReplacement expressionReplacement in expressionReplacements) { if (expressionReplacement.TryReplaceArgument(args.ModelTreeManager, validationService)) { argumentReplacementOccurred = true; } } if (argumentReplacementOccurred) { args.Handled = true; editingScope.Complete(); } } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } // We handle exception here instead of letting WF Designer handle it, so that the validation below will run even // if any of the ArgumentAccessor.Setter methods throw exceptions. ErrorReporting.ShowErrorMessage(e); } // Since any pending validation will be canceled if argument replacement occured (=has model change), we need to re-validate the workflow. // We suppressed validation upon EditingScope completion and do it ourselves, because // the argument replacement could have been done directly to the underlying activity instance, rather than thru ModelItem. if (argumentReplacementOccurred) { validationService.ValidateWorkflow(); } } } }
internal static ExpressionReplacement ComputeExpressionReplacement(ActivityWithResult expression, Activity parentActivity, EditingContext context, ArgumentAccessorWrapperCache argumentAccessorWrapperCache, Type preferredReturnType = null) { Fx.Assert(expression != null, "expressions cannot be null."); Fx.Assert(parentActivity != null, "parentActivity cannot be null."); Fx.Assert(context != null, "context cannot be null."); Fx.Assert(argumentAccessorWrapperCache != null, "argumentAccessorWrapperCache cannot be null."); IEnumerable<ArgumentAccessorWrapper> argumentAccessorWrappers = argumentAccessorWrapperCache.GetArgumentAccessorWrappers(parentActivity); if (argumentAccessorWrappers != null) { ArgumentAccessorWrapper argumentAccessorWrapper = argumentAccessorWrappers.FirstOrDefault(wrapper => object.ReferenceEquals(wrapper.Argument.Expression, expression)); if (argumentAccessorWrapper != null) { bool isLocationExpression = ExpressionHelper.IsGenericLocationExpressionType(expression); bool canInferType = true; Type expectedReturnType; ActivityWithResult morphedExpression; if (preferredReturnType != null) { expectedReturnType = preferredReturnType; } else { canInferType = ExpressionHelper.TryInferReturnType(expression, context, out expectedReturnType); } if (canInferType && expectedReturnType != null && ExpressionHelper.TryMorphExpression(expression, isLocationExpression, expectedReturnType, context, out morphedExpression)) { Type expressionResultType = isLocationExpression ? expression.ResultType.GetGenericArguments()[0] : expression.ResultType; if (expressionResultType != expectedReturnType) { Argument newArgument = Argument.Create(expectedReturnType, argumentAccessorWrapper.Argument.Direction); newArgument.Expression = morphedExpression; return new ExpressionReplacement(expression, argumentAccessorWrapper.Argument, newArgument, argumentAccessorWrapper.ArgumentAccessor); } } } } return null; }
internal static List<ExpressionReplacement> ComputeExpressionReplacements(IEnumerable<ActivityWithResult> expressions, EditingContext context, ArgumentAccessorWrapperCache argumentAccessorWrapperCache) { Fx.Assert(expressions != null, "expressions cannot be null."); Fx.Assert(context != null, "context cannot be null."); Fx.Assert(argumentAccessorWrapperCache != null, "argumentAccessorWrapperCache cannot be null."); HashSet<Assign> assignsWithRValueToFix = new HashSet<Assign>(); Dictionary<Assign, Type> assignLValueTypes = new Dictionary<Assign, Type>(); List<ExpressionReplacement> expressionReplacements = new List<ExpressionReplacement>(); foreach (ActivityWithResult expression in expressions) { Activity parentActivity = ValidationService.GetParent(expression); if (parentActivity == null) { continue; } Assign assignActivity = parentActivity as Assign; if (assignActivity != null && !ExpressionHelper.IsGenericLocationExpressionType(expression)) { assignsWithRValueToFix.Add(assignActivity); } else { ExpressionReplacement expressionReplacement = ComputeExpressionReplacement(expression, parentActivity, context, argumentAccessorWrapperCache); if (expressionReplacement != null) { expressionReplacements.Add(expressionReplacement); if (assignActivity != null) { Type expectedReturnType = expressionReplacement.NewArgument.ArgumentType; assignLValueTypes[assignActivity] = expectedReturnType; } } } } // Special handle Assign R-Values: Assign.To.ArgumentType must be the same as Assign.Value.ArgumentType. foreach (Assign assignWithRValueToFix in assignsWithRValueToFix) { Type expectedReturnType; if (assignLValueTypes.TryGetValue(assignWithRValueToFix, out expectedReturnType)) { assignLValueTypes.Remove(assignWithRValueToFix); } else if (assignWithRValueToFix.To != null) { expectedReturnType = assignWithRValueToFix.To.ArgumentType; } ExpressionReplacement expressionReplacement = ComputeExpressionReplacement(assignWithRValueToFix.Value.Expression, assignWithRValueToFix, context, argumentAccessorWrapperCache, expectedReturnType); if (expressionReplacement != null) { expressionReplacements.Add(expressionReplacement); } } // These Assign activities have their L-value argument (To) changed but not the R-value argument (Value). // Now make sure that the R-value arguments are compatible with the L-value argument. foreach (KeyValuePair<Assign, Type> kvp in assignLValueTypes) { Assign remainingAssign = kvp.Key; Type expectedReturnType = kvp.Value; if (remainingAssign.Value != null && remainingAssign.Value.Expression != null) { ActivityWithResult expression = remainingAssign.Value.Expression; if (expression.ResultType != expectedReturnType) { ExpressionReplacement expressionReplacement = ComputeExpressionReplacement(expression, remainingAssign, context, argumentAccessorWrapperCache, expectedReturnType); if (expressionReplacement != null) { expressionReplacements.Add(expressionReplacement); } } } } return expressionReplacements; }
internal static void UpdateInvalidArgumentsIfNecessary(object sender, ValidationService.ErrorsMarkedEventArgs args) { if (args.Reason != ValidationReason.ModelChange) { return; } if (args.Errors.Count == 0) { return; } using (EditingScope editingScope = args.ModelTreeManager.CreateEditingScope(string.Empty)) { // Prevent the validation -> fix arguments -> validation loop. editingScope.SuppressUndo = true; // Suppress validation. We will do it ourselves (see below) editingScope.SuppressValidationOnComplete = true; // Re-compile erroreous expressions to see if update is necessary ValidationService validationService = args.Context.Services.GetRequiredService <ValidationService>(); ArgumentAccessorWrapperCache argumentAccessorWrapperCache = new ArgumentAccessorWrapperCache(); List <ExpressionReplacement> expressionReplacements = ComputeExpressionReplacements(args.Errors.Select(error => error.Source).OfType <ActivityWithResult>(), args.Context, argumentAccessorWrapperCache); bool argumentReplacementOccurred = false; if (expressionReplacements.Count > 0) { try { foreach (ExpressionReplacement expressionReplacement in expressionReplacements) { if (expressionReplacement.TryReplaceArgument(args.ModelTreeManager, validationService)) { argumentReplacementOccurred = true; } } if (argumentReplacementOccurred) { args.Handled = true; editingScope.Complete(); } } catch (Exception e) { if (Fx.IsFatal(e)) { throw; } // We handle exception here instead of letting WF Designer handle it, so that the validation below will run even // if any of the ArgumentAccessor.Setter methods throw exceptions. ErrorReporting.ShowErrorMessage(e); } // Since any pending validation will be canceled if argument replacement occured (=has model change), we need to re-validate the workflow. // We suppressed validation upon EditingScope completion and do it ourselves, because // the argument replacement could have been done directly to the underlying activity instance, rather than thru ModelItem. if (argumentReplacementOccurred) { validationService.ValidateWorkflow(); } } } }
internal static ExpressionReplacement ComputeExpressionReplacement(ActivityWithResult expression, Activity parentActivity, EditingContext context, ArgumentAccessorWrapperCache argumentAccessorWrapperCache, Type preferredReturnType = null) { Fx.Assert(expression != null, "expressions cannot be null."); Fx.Assert(parentActivity != null, "parentActivity cannot be null."); Fx.Assert(context != null, "context cannot be null."); Fx.Assert(argumentAccessorWrapperCache != null, "argumentAccessorWrapperCache cannot be null."); IEnumerable <ArgumentAccessorWrapper> argumentAccessorWrappers = argumentAccessorWrapperCache.GetArgumentAccessorWrappers(parentActivity); if (argumentAccessorWrappers != null) { ArgumentAccessorWrapper argumentAccessorWrapper = argumentAccessorWrappers.FirstOrDefault(wrapper => object.ReferenceEquals(wrapper.Argument.Expression, expression)); if (argumentAccessorWrapper != null) { bool isLocationExpression = ExpressionHelper.IsGenericLocationExpressionType(expression); bool canInferType = true; Type expectedReturnType; ActivityWithResult morphedExpression; if (preferredReturnType != null) { expectedReturnType = preferredReturnType; } else { canInferType = ExpressionHelper.TryInferReturnType(expression, context, out expectedReturnType); } if (canInferType && expectedReturnType != null && ExpressionHelper.TryMorphExpression(expression, isLocationExpression, expectedReturnType, context, out morphedExpression)) { Type expressionResultType = isLocationExpression ? expression.ResultType.GetGenericArguments()[0] : expression.ResultType; if (expressionResultType != expectedReturnType) { Argument newArgument = Argument.Create(expectedReturnType, argumentAccessorWrapper.Argument.Direction); newArgument.Expression = morphedExpression; return(new ExpressionReplacement(expression, argumentAccessorWrapper.Argument, newArgument, argumentAccessorWrapper.ArgumentAccessor)); } } } } return(null); }
internal static List <ExpressionReplacement> ComputeExpressionReplacements(IEnumerable <ActivityWithResult> expressions, EditingContext context, ArgumentAccessorWrapperCache argumentAccessorWrapperCache) { Fx.Assert(expressions != null, "expressions cannot be null."); Fx.Assert(context != null, "context cannot be null."); Fx.Assert(argumentAccessorWrapperCache != null, "argumentAccessorWrapperCache cannot be null."); HashSet <Assign> assignsWithRValueToFix = new HashSet <Assign>(); Dictionary <Assign, Type> assignLValueTypes = new Dictionary <Assign, Type>(); List <ExpressionReplacement> expressionReplacements = new List <ExpressionReplacement>(); foreach (ActivityWithResult expression in expressions) { Activity parentActivity = ValidationService.GetParent(expression); if (parentActivity == null) { continue; } Assign assignActivity = parentActivity as Assign; if (assignActivity != null && !ExpressionHelper.IsGenericLocationExpressionType(expression)) { assignsWithRValueToFix.Add(assignActivity); } else { ExpressionReplacement expressionReplacement = ComputeExpressionReplacement(expression, parentActivity, context, argumentAccessorWrapperCache); if (expressionReplacement != null) { expressionReplacements.Add(expressionReplacement); if (assignActivity != null) { Type expectedReturnType = expressionReplacement.NewArgument.ArgumentType; assignLValueTypes[assignActivity] = expectedReturnType; } } } } // Special handle Assign R-Values: Assign.To.ArgumentType must be the same as Assign.Value.ArgumentType. foreach (Assign assignWithRValueToFix in assignsWithRValueToFix) { Type expectedReturnType; if (assignLValueTypes.TryGetValue(assignWithRValueToFix, out expectedReturnType)) { assignLValueTypes.Remove(assignWithRValueToFix); } else if (assignWithRValueToFix.To != null) { expectedReturnType = assignWithRValueToFix.To.ArgumentType; } ExpressionReplacement expressionReplacement = ComputeExpressionReplacement(assignWithRValueToFix.Value.Expression, assignWithRValueToFix, context, argumentAccessorWrapperCache, expectedReturnType); if (expressionReplacement != null) { expressionReplacements.Add(expressionReplacement); } } // These Assign activities have their L-value argument (To) changed but not the R-value argument (Value). // Now make sure that the R-value arguments are compatible with the L-value argument. foreach (KeyValuePair <Assign, Type> kvp in assignLValueTypes) { Assign remainingAssign = kvp.Key; Type expectedReturnType = kvp.Value; if (remainingAssign.Value != null && remainingAssign.Value.Expression != null) { ActivityWithResult expression = remainingAssign.Value.Expression; if (expression.ResultType != expectedReturnType) { ExpressionReplacement expressionReplacement = ComputeExpressionReplacement(expression, remainingAssign, context, argumentAccessorWrapperCache, expectedReturnType); if (expressionReplacement != null) { expressionReplacements.Add(expressionReplacement); } } } } return(expressionReplacements); }