1 //----------------------------------------------------------------
2 // <copyright company="Microsoft Corporation">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //----------------------------------------------------------------
7 namespace System.Activities.Presentation.Metadata
9 using System.Activities.Presentation.Model;
10 using System.Activities.Presentation.Validation;
11 using System.Activities.Statements;
12 using System.Activities.Validation;
13 using System.Collections.Generic;
14 using System.Globalization;
19 /// A helper class to provide additional functionalities regarding Activity arguments.
21 public static class ActivityArgumentHelper
23 private static Dictionary<Type, Func<Activity, IEnumerable<ArgumentAccessor>>> argumentAccessorsGenerators = new Dictionary<Type, Func<Activity, IEnumerable<ArgumentAccessor>>>();
26 /// Registers with an activity type a function to generate a list of ArgumentAccessors.
28 /// <param name="activityType">The activity type.</param>
29 /// <param name="argumentAccessorsGenerator">A function which takes in an activity instance (of type activityType) and returns a list of ArgumentAccessors.</param>
30 public static void RegisterAccessorsGenerator(Type activityType, Func<Activity, IEnumerable<ArgumentAccessor>> argumentAccessorsGenerator)
32 if (activityType == null)
34 throw FxTrace.Exception.ArgumentNull("activityType");
37 if (argumentAccessorsGenerator == null)
39 throw FxTrace.Exception.ArgumentNull("argumentAccessorsGenerator");
42 // IsAssignableFrom() returns true if activityType is the generic type definition inheriting from Activity.
43 if (!typeof(Activity).IsAssignableFrom(activityType))
45 throw FxTrace.Exception.Argument("activityType", string.Format(CultureInfo.CurrentCulture, SR.TypeDoesNotInheritFromActivity, activityType.Name));
48 argumentAccessorsGenerators[activityType] = argumentAccessorsGenerator;
51 internal static bool TryGetArgumentAccessorsGenerator(Type activityType, out Func<Activity, IEnumerable<ArgumentAccessor>> argumentAccessorsGenerator)
53 Fx.Assert(activityType != null, "activityType cannot be null.");
55 bool argumentAccessorsGeneratorFound = argumentAccessorsGenerators.TryGetValue(activityType, out argumentAccessorsGenerator);
56 if (!argumentAccessorsGeneratorFound && activityType.IsGenericType && !activityType.IsGenericTypeDefinition)
58 argumentAccessorsGeneratorFound = argumentAccessorsGenerators.TryGetValue(activityType.GetGenericTypeDefinition(), out argumentAccessorsGenerator);
61 return argumentAccessorsGeneratorFound;
64 internal static void UpdateInvalidArgumentsIfNecessary(object sender, ValidationService.ErrorsMarkedEventArgs args)
66 if (args.Reason != ValidationReason.ModelChange)
71 if (args.Errors.Count == 0)
76 using (EditingScope editingScope = args.ModelTreeManager.CreateEditingScope(string.Empty))
78 // Prevent the validation -> fix arguments -> validation loop.
79 editingScope.SuppressUndo = true;
81 // Suppress validation. We will do it ourselves (see below)
82 editingScope.SuppressValidationOnComplete = true;
84 // Re-compile erroreous expressions to see if update is necessary
85 ValidationService validationService = args.Context.Services.GetRequiredService<ValidationService>();
86 ArgumentAccessorWrapperCache argumentAccessorWrapperCache = new ArgumentAccessorWrapperCache();
87 List<ExpressionReplacement> expressionReplacements = ComputeExpressionReplacements(args.Errors.Select(error => error.Source).OfType<ActivityWithResult>(), args.Context, argumentAccessorWrapperCache);
88 bool argumentReplacementOccurred = false;
89 if (expressionReplacements.Count > 0)
93 foreach (ExpressionReplacement expressionReplacement in expressionReplacements)
95 if (expressionReplacement.TryReplaceArgument(args.ModelTreeManager, validationService))
97 argumentReplacementOccurred = true;
101 if (argumentReplacementOccurred)
104 editingScope.Complete();
114 // We handle exception here instead of letting WF Designer handle it, so that the validation below will run even
115 // if any of the ArgumentAccessor.Setter methods throw exceptions.
116 ErrorReporting.ShowErrorMessage(e);
119 // Since any pending validation will be canceled if argument replacement occured (=has model change), we need to re-validate the workflow.
120 // We suppressed validation upon EditingScope completion and do it ourselves, because
121 // the argument replacement could have been done directly to the underlying activity instance, rather than thru ModelItem.
122 if (argumentReplacementOccurred)
124 validationService.ValidateWorkflow();
130 internal static List<ExpressionReplacement> ComputeExpressionReplacements(IEnumerable<ActivityWithResult> expressions, EditingContext context, ArgumentAccessorWrapperCache argumentAccessorWrapperCache)
132 Fx.Assert(expressions != null, "expressions cannot be null.");
133 Fx.Assert(context != null, "context cannot be null.");
134 Fx.Assert(argumentAccessorWrapperCache != null, "argumentAccessorWrapperCache cannot be null.");
136 HashSet<Assign> assignsWithRValueToFix = new HashSet<Assign>();
137 Dictionary<Assign, Type> assignLValueTypes = new Dictionary<Assign, Type>();
138 List<ExpressionReplacement> expressionReplacements = new List<ExpressionReplacement>();
139 foreach (ActivityWithResult expression in expressions)
141 Activity parentActivity = ValidationService.GetParent(expression);
142 if (parentActivity == null)
147 Assign assignActivity = parentActivity as Assign;
148 if (assignActivity != null && !ExpressionHelper.IsGenericLocationExpressionType(expression))
150 assignsWithRValueToFix.Add(assignActivity);
154 ExpressionReplacement expressionReplacement = ComputeExpressionReplacement(expression, parentActivity, context, argumentAccessorWrapperCache);
155 if (expressionReplacement != null)
157 expressionReplacements.Add(expressionReplacement);
158 if (assignActivity != null)
160 Type expectedReturnType = expressionReplacement.NewArgument.ArgumentType;
161 assignLValueTypes[assignActivity] = expectedReturnType;
167 // Special handle Assign R-Values: Assign.To.ArgumentType must be the same as Assign.Value.ArgumentType.
168 foreach (Assign assignWithRValueToFix in assignsWithRValueToFix)
170 Type expectedReturnType;
171 if (assignLValueTypes.TryGetValue(assignWithRValueToFix, out expectedReturnType))
173 assignLValueTypes.Remove(assignWithRValueToFix);
175 else if (assignWithRValueToFix.To != null)
177 expectedReturnType = assignWithRValueToFix.To.ArgumentType;
180 ExpressionReplacement expressionReplacement = ComputeExpressionReplacement(assignWithRValueToFix.Value.Expression, assignWithRValueToFix, context, argumentAccessorWrapperCache, expectedReturnType);
181 if (expressionReplacement != null)
183 expressionReplacements.Add(expressionReplacement);
187 // These Assign activities have their L-value argument (To) changed but not the R-value argument (Value).
188 // Now make sure that the R-value arguments are compatible with the L-value argument.
189 foreach (KeyValuePair<Assign, Type> kvp in assignLValueTypes)
191 Assign remainingAssign = kvp.Key;
192 Type expectedReturnType = kvp.Value;
193 if (remainingAssign.Value != null && remainingAssign.Value.Expression != null)
195 ActivityWithResult expression = remainingAssign.Value.Expression;
196 if (expression.ResultType != expectedReturnType)
198 ExpressionReplacement expressionReplacement = ComputeExpressionReplacement(expression, remainingAssign, context, argumentAccessorWrapperCache, expectedReturnType);
199 if (expressionReplacement != null)
201 expressionReplacements.Add(expressionReplacement);
207 return expressionReplacements;
210 internal static ExpressionReplacement ComputeExpressionReplacement(ActivityWithResult expression, Activity parentActivity, EditingContext context, ArgumentAccessorWrapperCache argumentAccessorWrapperCache, Type preferredReturnType = null)
212 Fx.Assert(expression != null, "expressions cannot be null.");
213 Fx.Assert(parentActivity != null, "parentActivity cannot be null.");
214 Fx.Assert(context != null, "context cannot be null.");
215 Fx.Assert(argumentAccessorWrapperCache != null, "argumentAccessorWrapperCache cannot be null.");
217 IEnumerable<ArgumentAccessorWrapper> argumentAccessorWrappers = argumentAccessorWrapperCache.GetArgumentAccessorWrappers(parentActivity);
218 if (argumentAccessorWrappers != null)
220 ArgumentAccessorWrapper argumentAccessorWrapper = argumentAccessorWrappers.FirstOrDefault(wrapper => object.ReferenceEquals(wrapper.Argument.Expression, expression));
221 if (argumentAccessorWrapper != null)
223 bool isLocationExpression = ExpressionHelper.IsGenericLocationExpressionType(expression);
224 bool canInferType = true;
225 Type expectedReturnType;
226 ActivityWithResult morphedExpression;
227 if (preferredReturnType != null)
229 expectedReturnType = preferredReturnType;
233 canInferType = ExpressionHelper.TryInferReturnType(expression, context, out expectedReturnType);
236 if (canInferType && expectedReturnType != null && ExpressionHelper.TryMorphExpression(expression, isLocationExpression, expectedReturnType, context, out morphedExpression))
238 Type expressionResultType = isLocationExpression ? expression.ResultType.GetGenericArguments()[0] : expression.ResultType;
239 if (expressionResultType != expectedReturnType)
241 Argument newArgument = Argument.Create(expectedReturnType, argumentAccessorWrapper.Argument.Direction);
242 newArgument.Expression = morphedExpression;
243 return new ExpressionReplacement(expression, argumentAccessorWrapper.Argument, newArgument, argumentAccessorWrapper.ArgumentAccessor);
252 internal sealed class ArgumentAccessorWrapper
254 public ArgumentAccessorWrapper(ArgumentAccessor argumentAccessor, Argument argument)
256 Fx.Assert(argumentAccessor != null, "argumentAccessor cannot be null.");
257 Fx.Assert(argument != null, "argument cannot be null.");
259 this.ArgumentAccessor = argumentAccessor;
260 this.Argument = argument;
263 public ArgumentAccessor ArgumentAccessor { get; private set; }
265 public Argument Argument { get; private set; }
268 // This cache ensures that during the entire argument-fixing process (i.e. within UpdateInvalidArgumentsIfNecessary):
269 // 1) An argument accessor generator function is called on an activity instance at most once.
270 // 2) An argument accessor Getter function is called on an activity instance at most once.
271 internal sealed class ArgumentAccessorWrapperCache
273 private Dictionary<Activity, List<ArgumentAccessorWrapper>> argumentAccessorWrappersCache;
275 public ArgumentAccessorWrapperCache()
277 this.argumentAccessorWrappersCache = new Dictionary<Activity, List<ArgumentAccessorWrapper>>();
280 public List<ArgumentAccessorWrapper> GetArgumentAccessorWrappers(Activity activity)
282 Fx.Assert(activity != null, "activity cannot be null.");
284 List<ArgumentAccessorWrapper> argumentAccessorWrappers = null;
285 if (!this.argumentAccessorWrappersCache.TryGetValue(activity, out argumentAccessorWrappers))
287 Func<Activity, IEnumerable<ArgumentAccessor>> argumentAccessorsGenerator;
288 if (ActivityArgumentHelper.TryGetArgumentAccessorsGenerator(activity.GetType(), out argumentAccessorsGenerator))
290 IEnumerable<ArgumentAccessor> argumentAccessors = argumentAccessorsGenerator(activity);
291 if (argumentAccessors != null)
293 argumentAccessorWrappers = new List<ArgumentAccessorWrapper>();
294 foreach (ArgumentAccessor argumentAccessor in argumentAccessors)
296 if (argumentAccessor != null && argumentAccessor.Getter != null)
298 Argument argument = argumentAccessor.Getter(activity);
299 if (argument != null)
301 ArgumentAccessorWrapper argumentAccessorWrapper = new ArgumentAccessorWrapper(argumentAccessor, argument);
302 argumentAccessorWrappers.Add(argumentAccessorWrapper);
307 this.argumentAccessorWrappersCache.Add(activity, argumentAccessorWrappers);
312 return argumentAccessorWrappers;
316 internal sealed class ExpressionReplacement
318 public ExpressionReplacement(ActivityWithResult expressionToReplace, Argument oldArgument, Argument newArgument, ArgumentAccessor argumentAccessor)
320 Fx.Assert(expressionToReplace != null, "expressionToReplace cannot be null.");
321 Fx.Assert(oldArgument != null, "oldArgument cannot be null.");
322 Fx.Assert(newArgument != null, "newArgument cannot be null.");
323 Fx.Assert(argumentAccessor != null, "argumentAccessor cannot be null.");
325 this.ExpressionToReplace = expressionToReplace;
326 this.OldArgument = oldArgument;
327 this.NewArgument = newArgument;
328 this.ArgumentAccessor = argumentAccessor;
331 public ActivityWithResult ExpressionToReplace
337 public Argument OldArgument
343 public Argument NewArgument
349 public ArgumentAccessor ArgumentAccessor
355 public bool TryReplaceArgument(ModelTreeManager modelTreeManager, ValidationService validationService)
357 Fx.Assert(modelTreeManager != null, "modelTreeManager cannot be null.");
358 Fx.Assert(validationService != null, "validationService cannot be null.");
360 ModelItem expressionModelItem = modelTreeManager.GetModelItem(this.ExpressionToReplace);
361 if (expressionModelItem != null)
363 ModelItem argumentModelItem = expressionModelItem.Parent;
364 ModelItem parentObject = argumentModelItem.Parent;
366 if (argumentModelItem.Source != null)
368 ModelProperty argumentProperty = parentObject.Properties[argumentModelItem.Source.Name];
369 Type argumentPropertyType = argumentProperty.PropertyType;
370 if (argumentPropertyType == typeof(InArgument) || argumentPropertyType == typeof(OutArgument) || argumentPropertyType == typeof(InOutArgument))
372 ModelItem oldArgumentModel = argumentProperty.Value;
373 ModelItem newArgumentModel = argumentProperty.SetValue(this.NewArgument);
375 // Make sure argument.Expression is wrapped in ModelItem as well
376 ModelItem newExpressionModel = newArgumentModel.Properties["Expression"].Value;
384 Activity parentActivity = ValidationService.GetParent(this.ExpressionToReplace);
385 if (this.ArgumentAccessor.Setter != null)
389 validationService.DeactivateValidation();
390 this.ArgumentAccessor.Setter(parentActivity, this.NewArgument);
395 validationService.ActivateValidation();