2 // Copyright (c) Microsoft Corporation. All rights reserved.
5 namespace System.Activities.Presentation.Validation
7 using System.Activities.Expressions;
8 using System.Activities.Presentation.Model;
9 using System.Activities.Presentation.Services;
10 using System.Activities.Presentation.View;
11 using System.Activities.Validation;
12 using System.Collections.Generic;
13 using System.Diagnostics.CodeAnalysis;
14 using System.Globalization;
15 using System.Reflection;
17 using System.ServiceModel.Activities;
19 using System.Threading;
20 using System.Windows.Threading;
21 using Microsoft.Activities.Presentation.Xaml;
22 using Microsoft.Win32;
24 [Fx.Tag.XamlVisible(false)]
25 public class ValidationService
27 static PropertyInfo parentPropertyInfo;
29 Dictionary<Type, IValidationErrorSourceLocator> validationErrorSourceLocators;
30 List<Guid> acquiredObjectReferences;
32 EditingContext context;
33 ModelService modelService;
34 ModelSearchServiceImpl modelSearchService;
35 ModelTreeManager modelTreeManager;
36 WorkflowViewService viewService;
37 IValidationErrorService errorService;
38 ObjectReferenceService objectReferenceService;
39 TaskDispatcher validationTaskDispatcher;
40 ValidationSynchronizer validationSynchronizer;
42 // Dictionary which maps the object to their error messages and indicators
43 // NOTE: Valid objects do not appear in this dictionary,
44 // only elements which violated a constraint (Errors or Warnings or Child Validation Issues)
45 Dictionary<object, ValidationErrorState> validationErrors;
47 // Attached properties for error visuals
48 AttachedProperty<ValidationState> validationStateProperty;
49 AttachedProperty<string> validationMessageProperty;
51 internal event EventHandler ValidationCompleted;
53 internal class ErrorsMarkedEventArgs : EventArgs
55 ICollection<ValidationError> errors;
56 ValidationReason reason;
57 ModelTreeManager modelTreeManager;
58 EditingContext context;
60 public ErrorsMarkedEventArgs(ICollection<ValidationError> errors,
61 ValidationReason reason,
62 ModelTreeManager modelTreeManager,
63 EditingContext context)
67 this.modelTreeManager = modelTreeManager;
68 this.context = context;
71 public ICollection<ValidationError> Errors
73 get { return this.errors; }
76 public ValidationReason Reason
78 get { return this.reason; }
81 public ModelTreeManager ModelTreeManager
83 get { return this.modelTreeManager; }
92 public EditingContext Context
94 get { return this.context; }
98 internal event EventHandler<ErrorsMarkedEventArgs> ErrorsMarked;
100 DynamicActivity dynamicActivityWrapper;
102 ValidationSettings settings;
104 bool isValidationDisabled = false;
105 const string ValidationRegKeyName = "DisableValidateOnModelItemChanged";
106 const string ValidationRegKeyInitialPath = "Software\\Microsoft\\.NETFramework\\";
108 static StringBuilder errorBuilder;
110 private static StringBuilder ErrorBuilder
114 if (errorBuilder == null)
116 errorBuilder = new StringBuilder();
122 public ValidationService(EditingContext context)
123 : this(context, new TaskDispatcher())
127 internal ValidationService(EditingContext context, TaskDispatcher validationTaskDispatcher)
129 Fx.Assert(validationTaskDispatcher != null, "validationTaskDispatcher cannot be null.");
130 this.validationTaskDispatcher = validationTaskDispatcher;
131 this.context = context;
132 this.settings = new ValidationSettings { SkipValidatingRootConfiguration = true };
133 this.context.Services.Subscribe<ModelService>(new SubscribeServiceCallback<ModelService>(OnModelServiceAvailable));
134 this.context.Services.Subscribe<ModelSearchService>(new SubscribeServiceCallback<ModelSearchService>(OnModelSearchServiceAvailable));
135 this.context.Services.Subscribe<ObjectReferenceService>(new SubscribeServiceCallback<ObjectReferenceService>(OnObjectReferenceServiceAvailable));
136 this.context.Services.Subscribe<ModelTreeManager>(new SubscribeServiceCallback<ModelTreeManager>(OnModelTreeManagerAvailable));
137 this.context.Services.Subscribe<IValidationErrorService>(new SubscribeServiceCallback<IValidationErrorService>(OnErrorServiceAvailable));
138 this.context.Services.Subscribe<AttachedPropertiesService>(new SubscribeServiceCallback<AttachedPropertiesService>(OnAttachedPropertiesServiceAvailable));
139 AssemblyName currentAssemblyName = Assembly.GetExecutingAssembly().GetName();
140 StringBuilder validationKeyPath = new StringBuilder(90);
141 validationKeyPath.Append(ValidationRegKeyInitialPath);
142 validationKeyPath.AppendFormat("{0}{1}{2}", "v", currentAssemblyName.Version.ToString(), "\\");
143 validationKeyPath.Append(currentAssemblyName.Name);
145 RegistryKey validationRegistryKey = Registry.CurrentUser.OpenSubKey(validationKeyPath.ToString());
146 if (validationRegistryKey != null)
148 object value = validationRegistryKey.GetValue(ValidationRegKeyName);
150 this.isValidationDisabled = (value != null && string.Equals("1", value.ToString()));
152 validationRegistryKey.Close();
156 private ValidationSynchronizer ValidationSynchronizer
160 if (this.validationSynchronizer == null)
162 if (DesignerConfigurationServiceUtilities.IsBackgroundValidationEnabled(context))
164 this.validationSynchronizer = new BackgroundValidationSynchronizer<Tuple<ValidationReason, ValidationResults, Exception>>(validationTaskDispatcher, this.CoreValidationWork, this.OnValidationWorkCompleted);
168 this.validationSynchronizer = new ForegroundValidationSynchronizer<Tuple<ValidationReason, ValidationResults, Exception>>(validationTaskDispatcher, this.CoreValidationWork, this.OnValidationWorkCompleted);
172 return this.validationSynchronizer;
176 internal DynamicActivity DynamicActivityWrapper
180 if (null == this.dynamicActivityWrapper)
182 this.dynamicActivityWrapper = new DynamicActivity();
184 return this.dynamicActivityWrapper;
188 public ValidationSettings Settings
192 return this.settings;
196 WorkflowViewService ViewService
200 if (null == this.viewService)
202 this.viewService = (WorkflowViewService)this.context.Services.GetService<ViewService>();
204 return this.viewService;
208 void OnAttachedPropertiesServiceAvailable(AttachedPropertiesService attachedPropertiesService)
210 this.validationStateProperty = new AttachedProperty<ValidationState>()
212 Getter = (modelItem) => GetValidationState(modelItem),
213 Name = "ValidationState",
214 OwnerType = typeof(object)
217 attachedPropertiesService.AddProperty(this.validationStateProperty);
219 this.validationMessageProperty = new AttachedProperty<string>()
221 Getter = (modelItem) => GetValidationMessage(modelItem),
222 Name = "ValidationMessage",
223 OwnerType = typeof(object)
226 attachedPropertiesService.AddProperty(this.validationMessageProperty);
229 ValidationState GetValidationState(ModelItem modelItem)
231 ValidationState validationState = ValidationState.Valid;
232 ValidationErrorState validationError = GetValidationError(modelItem);
234 if (validationError != null)
236 validationState = validationError.ValidationState;
238 return validationState;
241 string GetValidationMessage(ModelItem modelItem)
243 string errorMessage = string.Empty;
244 ValidationErrorState validationError = GetValidationError(modelItem);
246 if (validationError != null)
248 if (validationError.ErrorMessages != null)
250 ValidationService.ErrorBuilder.Clear();
251 foreach (string message in validationError.ErrorMessages)
253 ValidationService.ErrorBuilder.AppendLine(message.Trim());
255 errorMessage = ValidationService.ErrorBuilder.ToString().Trim();
261 ValidationErrorState GetValidationError(ModelItem modelItem)
263 ValidationErrorState validationError = null;
264 this.ValidationErrors.TryGetValue(modelItem.GetCurrentValue(), out validationError);
265 return validationError;
268 void OnModelServiceAvailable(ModelService modelService)
270 if (modelService != null)
272 this.modelService = modelService;
276 void OnModelSearchServiceAvailable(ModelSearchService modelSearchService)
278 if (modelSearchService != null)
280 this.modelSearchService = modelSearchService as ModelSearchServiceImpl;
284 void OnObjectReferenceServiceAvailable(ObjectReferenceService objectReferenceService)
286 if (objectReferenceService != null)
288 this.objectReferenceService = objectReferenceService;
292 void OnModelTreeManagerAvailable(ModelTreeManager modelTreeManager)
294 if (modelTreeManager != null)
296 this.modelTreeManager = modelTreeManager;
300 void OnErrorServiceAvailable(IValidationErrorService errorService)
302 if (errorService != null)
304 this.errorService = errorService;
305 if (this.isValidationDisabled)
307 this.errorService.ShowValidationErrors(new List<ValidationErrorInfo> { new ValidationErrorInfo(new ValidationError(SR.ValidationDisabledWarning, true)) });
312 public void ValidateWorkflow()
314 ValidateWorkflow(ValidationReason.Unknown);
317 private ValidationRoot GetRootElement()
319 Activity rootElement = null;
321 Fx.Assert(this.modelService != null, "ModelService is null."); // ModelService should not be null
323 ModelItem rootItem = this.modelService.Root;
324 object root = rootItem.GetCurrentValue();
325 // special case for WorkflowService - it will be returned directly
326 WorkflowService workflowService = root as WorkflowService;
327 if (workflowService != null)
329 return new ValidationRoot(workflowService);
331 //special case for ActivityBuilder - its will be converted to a DynamicActivity before validation.
332 ActivityBuilder activityBuilder = root as ActivityBuilder;
333 if (activityBuilder != null)
335 ActivityBuilderExtensions.ConvertActivityBuilderToDynamicActivity(activityBuilder, this.DynamicActivityWrapper);
336 rootElement = this.DynamicActivityWrapper;
340 rootElement = rootItem.GetRootActivity();
343 IList<AssemblyReference> references;
344 IList<string> namespaces = NamespaceHelper.GetTextExpressionNamespaces(root, out references);
345 NamespaceHelper.SetTextExpressionNamespaces(rootElement, namespaces, references);
347 if (rootElement != null)
349 return new ValidationRoot(rootElement);
357 internal void ValidateWorkflow(ValidationReason reason)
359 if (this.isValidationDisabled)
364 this.validationTaskDispatcher.DispatchWorkOnUIThread(DispatcherPriority.ApplicationIdle, new Action(() =>
366 this.ValidationSynchronizer.Validate(reason);
370 internal void DeactivateValidation()
372 this.ValidationSynchronizer.DeactivateValidation();
375 internal void ActivateValidation()
377 this.ValidationSynchronizer.ActivateValidation();
380 [SuppressMessage(FxCop.Category.Design, FxCop.Rule.DoNotCatchGeneralExceptionTypes)]
381 internal Tuple<ValidationReason, ValidationResults, Exception> CoreValidationWork(ValidationReason reason, CancellationToken cancellationToken)
383 this.settings.CancellationToken = cancellationToken;
384 ValidationResults results = null;
385 Exception exception = null;
388 ValidationRoot rootElement = this.GetRootElement();
389 if (rootElement != null)
391 results = rootElement.Validate(this.Settings);
396 if (Fx.IsFatal(e) || e is OperationCanceledException)
404 return Tuple.Create(reason, results, exception);
407 private void OnValidationWorkCompleted(Tuple<ValidationReason, ValidationResults, Exception> input)
409 ValidationReason reason = input.Item1;
410 ValidationResults results = input.Item2;
411 Exception exception = input.Item3;
413 Fx.Assert(results != null ^ exception != null, "result and exception should not both be null");
415 bool needsToMarkValidationErrors = false;
416 ValidationErrorInfo validationErrorInfo = null;
417 if (exception != null)
419 ModelItem rootModelItem = this.modelService.Root;
420 Activity rootActivity = rootModelItem.GetRootActivity();
422 if (rootActivity != null)
424 // We don't want any crash propagating from here as it causes VS to crash.
425 if (!this.ValidationErrors.ContainsKey(rootActivity))
427 ValidationErrorState validationError = new ValidationErrorState(new List<string>(), ValidationState.Error);
428 this.ValidationErrors.Add(rootActivity, validationError);
432 this.ValidationErrors[rootActivity].ValidationState = ValidationState.Error;
435 this.ValidationErrors[rootActivity].ErrorMessages.Add(exception.ToString());
437 // Notify an update to the attached properties
438 this.NotifyValidationPropertiesChanged(rootModelItem);
441 validationErrorInfo = new ValidationErrorInfo(exception.ToString());
442 needsToMarkValidationErrors = true;
445 DesignerPerfEventProvider perfProvider = this.context.Services.GetService<DesignerPerfEventProvider>();
446 perfProvider.WorkflowDesignerValidationStart();
448 List<ValidationError> validationErrors = null;
451 validationErrors = new List<ValidationError>(results.Errors);
452 validationErrors.AddRange(results.Warnings);
453 Activity rootActivity = this.modelService.Root.GetRootActivity();
454 needsToMarkValidationErrors = this.MarkErrors(validationErrors, reason, rootActivity);
457 if (this.errorService != null && needsToMarkValidationErrors) // Error service could be null if no implementation has been provided
459 List<ValidationErrorInfo> errors = new List<ValidationErrorInfo>();
461 if (validationErrors != null)
463 foreach (ValidationError validationError in validationErrors)
465 Activity currentActivity = validationError.Source;
466 ValidationErrorInfo error = new ValidationErrorInfo(validationError);
468 // The acquired activity reference will be release in the Main AppDomain when it clear the error list
469 if (validationError.SourceDetail != null)
471 error.SourceReferenceId = this.objectReferenceService.AcquireObjectReference(validationError.SourceDetail);
473 else if (validationError.Source != null)
475 error.SourceReferenceId = this.objectReferenceService.AcquireObjectReference(validationError.Source);
479 error.SourceReferenceId = Guid.Empty;
485 if (validationErrorInfo != null)
487 errors.Add(validationErrorInfo);
490 foreach (Guid acquiredObjectReference in this.AcquiredObjectReferences)
492 this.objectReferenceService.ReleaseObjectReference(acquiredObjectReference);
495 this.AcquiredObjectReferences.Clear();
497 foreach (ValidationErrorInfo error in errors)
499 if (error.SourceReferenceId != Guid.Empty)
501 this.AcquiredObjectReferences.Add(error.SourceReferenceId);
505 this.errorService.ShowValidationErrors(errors);
508 perfProvider.WorkflowDesignerValidationEnd();
509 this.OnValidationCompleted();
512 protected virtual void OnValidationCompleted()
514 if (this.ValidationCompleted != null)
516 this.ValidationCompleted(this, new EventArgs());
520 // Find model item and properly create it if necessary.
521 internal static ModelItem FindModelItem(ModelTreeManager modelTreeManager, object sourceDetail)
523 if (sourceDetail == null)
528 Fx.Assert(modelTreeManager != null, "modelTreeManager != null");
530 Activity element = sourceDetail as Activity;
531 object errorTarget = sourceDetail;
533 // if source detail is not an Activity, we just expand the model tree to search it.
536 return ModelTreeManager.FindFirst(modelTreeManager.Root, (modelItem) => (modelItem.GetCurrentValue() == errorTarget));
540 return FindActivityModelItem(modelTreeManager, element);
544 internal static Activity GetParent(Activity childActivity)
546 // Obtaining the parent from childActivity using (private) reflection.
547 if (parentPropertyInfo == null)
549 parentPropertyInfo = typeof(Activity).GetProperty("Parent", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic);
551 Fx.Assert(parentPropertyInfo != null, "Activity.Parent is not defined");
552 return parentPropertyInfo.GetValue(childActivity, null) as Activity;
555 // Get the parent chain of this activity.
556 // Can't use GetParentChain activity because it can only be used in a Constraint.
557 internal static List<Activity> GetParentChain(Activity activity)
559 List<Activity> parentChain = new List<Activity>();
560 while (activity != null)
562 activity = GetParent(activity);
563 if (activity != null)
565 parentChain.Add(activity);
571 private List<object> GetParentChainWithSource(Activity activity)
573 List<object> parentChain = new List<object>();
574 parentChain.Add(activity);
575 while (activity != null)
577 activity = GetParent(activity);
578 if (activity != null)
580 IValidationErrorSourceLocator validationErrorSourceLocator = this.GetValidationErrorSourceLocator(activity.GetType());
581 if (validationErrorSourceLocator != null)
583 validationErrorSourceLocator.ReplaceParentChainWithSource(activity, parentChain);
587 parentChain.Add(activity);
592 parentChain.RemoveAt(0);
596 // Mark all the errors including their parent chains
597 private bool MarkErrors(ICollection<ValidationError> errors, ValidationReason reason, Activity rootActivity)
599 // Clear the previous errors/warnings and update the visuals
601 Fx.Assert(this.modelTreeManager != null, "ModelTreeManager is null."); // ModelTreeManager should not be null
603 if (this.HandleErrorsMarked(errors, reason))
608 // Iterate through the new violation list and mark errors/warnings
609 foreach (ValidationError error in errors)
611 if (error.Source != null)
613 List<object> errorSourcePath = this.GetValidationErrorSourcePath(error.Source, error.SourceDetail);
614 MarkError(error, errorSourcePath);
616 else if (error.SourceDetail != null && error.SourceDetail is Receive)
619 // WorkflowService.Validate() may produce ValidationError { isWarning = true, Source = null, SourceDetail = Receive activity }
621 List<object> errorSourcePath = this.GetValidationErrorSourcePath((Activity)error.SourceDetail, null);
622 MarkError(error, errorSourcePath);
624 else if (rootActivity != null)
626 List<object> errorSourcePath = this.GetValidationErrorSourcePath(rootActivity, error.SourceDetail);
627 MarkError(error, errorSourcePath);
634 // Mark a single error including its parent chain
635 private void MarkError(ValidationError validationError, List<object> errorSourcePath)
637 object errorSource = errorSourcePath[0];
638 this.MarkCulprit(errorSource, validationError);
640 // Intentionally skipping the zeroth errorSourcePath because that is the culprit and is marked above.
641 for (int errorSourcePathIndex = 1; errorSourcePathIndex < errorSourcePath.Count; errorSourcePathIndex++)
643 this.MarkParent(validationError, errorSourcePath[errorSourcePathIndex]);
646 foreach (object parent in this.GetParentChainWithSource(validationError.Source))
648 this.MarkParent(validationError, parent);
652 // Mark a single error on the culprit
653 private void MarkCulprit(object errorSource, ValidationError validationError)
655 ValidationErrorState currentError;
656 if (!this.ValidationErrors.TryGetValue(errorSource, out currentError))
658 currentError = new ValidationErrorState(new List<string>(), ValidationState.Valid);
659 this.ValidationErrors.Add(errorSource, currentError);
661 MergeValidationError(currentError, validationError);
662 this.NotifyValidationPropertiesChanged(errorSource);
665 // Mark a single "child has an error" on a parent
666 private void MarkParent(ValidationError validationError, object errorParent)
668 ValidationState childValidationState = GetValidationState(validationError);
669 ValidationErrorState currentError;
670 if (!this.ValidationErrors.TryGetValue(errorParent, out currentError))
672 currentError = ChildInvalidError();
673 this.ValidationErrors.Add(errorParent, currentError);
676 if (currentError.ValidationState < childValidationState)
678 currentError.ValidationState = childValidationState;
681 this.NotifyValidationPropertiesChanged(errorParent);
684 private void NotifyValidationPropertiesChanged(object errorItem)
686 ModelItem errorModelItem = this.modelTreeManager.GetModelItem(errorItem);
687 if (errorModelItem != null)
689 this.NotifyValidationPropertiesChanged(errorModelItem);
693 private void NotifyValidationPropertiesChanged(ModelItem modelItem)
695 // Notify an update to the attached properties
696 this.validationStateProperty.NotifyPropertyChanged(modelItem);
697 this.validationMessageProperty.NotifyPropertyChanged(modelItem);
700 private bool HandleErrorsMarked(ICollection<ValidationError> errors, ValidationReason reason)
702 if (this.ErrorsMarked != null)
704 ErrorsMarkedEventArgs arg = new ErrorsMarkedEventArgs(errors, reason, this.modelTreeManager, this.context);
705 this.ErrorsMarked(this, arg);
712 private static ModelItem FindActivityModelItem(ModelTreeManager modelTreeManager, Activity errorTarget)
714 // Search the lowest Activity
715 ModelItem lowestModelItem = null;
716 List<Activity> parentChain = GetParentChain(errorTarget);
717 Fx.Assert(parentChain != null, "Cannot find parent chain for " + errorTarget.DisplayName);
719 foreach (Activity parent in parentChain)
721 lowestModelItem = modelTreeManager.GetModelItem(parent);
722 if (lowestModelItem != null)
728 ModelItem foundItem = null;
729 // Find in nearest parent first.
730 if (lowestModelItem != null)
732 // The foundItem could be null because lowestModelItem is not errorTarget's parent any more.
733 // This happens if background validation hasn't finished updating errorTarget's parent.
734 foundItem = ModelTreeManager.FindFirst(lowestModelItem, (modelItem) => (modelItem.GetCurrentValue() == errorTarget));
737 // Not found, search from root.
738 if (foundItem == null)
740 foundItem = FindActivityModelItemFromRoot(modelTreeManager, errorTarget);
746 private static ModelItem FindActivityModelItemFromRoot(ModelTreeManager modelTreeManager, Activity errorTarget)
748 ModelItem root = modelTreeManager.Root;
749 Fx.Assert(root != null && errorTarget != null, "root != null && errorTarget != null");
750 ModelProperty property = root.Properties["Properties"];
752 ModelItem propertiesModelItem = property == null ? null : property.Value;
753 ModelItem foundItem = null;
754 if (propertiesModelItem != null)
756 // So,search "Properties" first to delay expanding "Implementation" and other properties.
757 foundItem = ModelTreeManager.FindFirst(propertiesModelItem, (modelItem) => (modelItem.GetCurrentValue() == errorTarget));
760 // If activity is not in Properties, expand others except Properties.
761 foundItem = foundItem ?? ModelTreeManager.FindFirst(
763 (modelItem) => (modelItem.GetCurrentValue() == errorTarget),
764 (modelItem) => { return modelItem != propertiesModelItem; });
769 private static ValidationState GetValidationState(ValidationError validationError)
771 return validationError.IsWarning ? ValidationState.Warning : ValidationState.Error;
774 private static ValidationErrorState ChildInvalidError()
776 return new ValidationErrorState(new List<string> { SR.ChildValidationError }, ValidationState.ChildInvalid);
779 private static void MergeValidationError(ValidationErrorState originalError, ValidationError newError)
781 if (originalError.ValidationState == ValidationState.ChildInvalid)
783 // If original error is due to child's issue, clear the error list,
784 // as we don't care about its child's issues anymore and want to add its own issues.
785 originalError.ErrorMessages.Clear();
788 ValidationState errorState = GetValidationState(newError);
789 if (originalError.ValidationState < errorState)
791 // Promote to the higher level of violation.
792 originalError.ValidationState = errorState;
795 if (newError.IsWarning)
797 originalError.ErrorMessages.Add(string.Format(CultureInfo.CurrentUICulture, SR.WarningFormat, newError.Message));
801 originalError.ErrorMessages.Add(newError.Message);
807 // Copy over the previously marked model items before you clear the dictionaries
808 object[] oldErrorList = new object[this.ValidationErrors.Count];
809 this.ValidationErrors.Keys.CopyTo(oldErrorList, 0);
811 this.ValidationErrors.Clear();
813 // Iterate through the previously marked model items and notify an update to the attached properties
815 foreach (object workflowElement in oldErrorList)
817 modelItem = this.modelTreeManager.GetModelItem(workflowElement);
818 if (modelItem != null)
820 NotifyValidationPropertiesChanged(modelItem);
825 public void NavigateToError(ValidationErrorInfo validationErrorInfo)
827 if (validationErrorInfo == null)
829 throw FxTrace.Exception.ArgumentNull("validationErrorInfo");
832 object sourceDetail = this.GetSourceDetail(validationErrorInfo);
833 this.NavigateToErrorOnDispatcherThread(sourceDetail);
836 private object GetSourceDetail(ValidationErrorInfo validationErrorInfo)
838 Fx.Assert(validationErrorInfo != null, "validationErrorInfo should not be null and is checked by caller.");
839 Guid sourceReferenceId = validationErrorInfo.SourceReferenceId;
840 object sourceDetail = null;
842 if (sourceReferenceId == Guid.Empty)
844 if (this.modelTreeManager.Root != null)
846 sourceDetail = modelTreeManager.Root.GetCurrentValue();
851 if (!this.objectReferenceService.TryGetObject(sourceReferenceId, out sourceDetail))
853 throw FxTrace.Exception.Argument("validationErrorInfo", string.Format(CultureInfo.CurrentUICulture, SR.SourceReferenceIdNotFoundInWorkflow, sourceReferenceId));
859 private void NavigateToErrorOnDispatcherThread(object sourceDetail)
861 this.validationTaskDispatcher.DispatchWorkOnUIThread(DispatcherPriority.ApplicationIdle, new Action(
864 this.NavigateToError(sourceDetail);
868 public void NavigateToError(string id)
872 throw FxTrace.Exception.ArgumentNull("id");
875 ValidationRoot rootElement = this.GetRootElement();
876 if (rootElement != null)
878 Activity errorElement = rootElement.Resolve(id);
879 this.NavigateToErrorOnDispatcherThread(errorElement);
883 void NavigateToError(object sourceDetail)
885 Fx.Assert(this.modelTreeManager != null, "ModelTreeManager is null.");
886 ModelItem modelItem = this.modelTreeManager.GetModelItem(sourceDetail) ?? FindModelItem(this.modelTreeManager, sourceDetail);
888 if (modelItem != null)
890 if (this.modelSearchService != null)
892 this.modelSearchService.NavigateTo(modelItem);
896 // For any Expression, need to focus to its parent instead.
897 Activity activity = modelItem.GetCurrentValue() as Activity;
898 if (activity != null && (activity.IsExpression()))
900 ModelItem parent = modelItem.Parent;
901 while (parent != null)
903 bool hasDesignerAttribute = this.ViewService.GetDesignerType(parent.ItemType) != null;
905 // ModelItemKeyValuePair type also has DesignerAttribute.
906 // Since we do not want to put a focus on that type, special-casing it here.
907 bool isModelItemKeyValuePair = parent.ItemType.IsGenericType &&
908 parent.ItemType.GetGenericTypeDefinition() == typeof(ModelItemKeyValuePair<,>);
910 if (hasDesignerAttribute && !isModelItemKeyValuePair)
915 parent = parent.Parent;
928 internal void RegisterValidationErrorSourceLocator(Type activityType, IValidationErrorSourceLocator validationErrorSourceLocator)
930 if (validationErrorSourceLocator == null)
932 throw FxTrace.Exception.ArgumentNull("validationErrorSourceLocator");
934 this.ValidationErrorSourceLocators.Add(activityType, validationErrorSourceLocator);
937 List<object> GetValidationErrorSourcePath(Activity violatingActivity, object sourceDetail)
939 IValidationErrorSourceLocator validationErrorSourceLocator = GetValidationErrorSourceLocator(violatingActivity.GetType());
940 if (validationErrorSourceLocator == null)
942 return new List<object> { violatingActivity };
946 return validationErrorSourceLocator.FindSourceDetailFromActivity(violatingActivity, sourceDetail);
950 IValidationErrorSourceLocator GetValidationErrorSourceLocator(Type typeOfActivityWithValidationError)
952 IValidationErrorSourceLocator validationErrorSourceLocator;
953 if (this.ValidationErrorSourceLocators.TryGetValue(typeOfActivityWithValidationError, out validationErrorSourceLocator))
955 Fx.Assert(validationErrorSourceLocator != null, "Ensured by RegisterValidationErrorSourceLocator");
956 return validationErrorSourceLocator;
958 else if (typeOfActivityWithValidationError.IsGenericType && !typeOfActivityWithValidationError.IsGenericTypeDefinition)
960 return this.GetValidationErrorSourceLocator(typeOfActivityWithValidationError.GetGenericTypeDefinition());
969 Dictionary<object, ValidationErrorState> ValidationErrors
973 if (this.validationErrors == null)
975 this.validationErrors = new Dictionary<object, ValidationErrorState>();
977 return this.validationErrors;
981 Dictionary<Type, IValidationErrorSourceLocator> ValidationErrorSourceLocators
985 if (this.validationErrorSourceLocators == null)
987 this.validationErrorSourceLocators = new Dictionary<Type, IValidationErrorSourceLocator>();
989 return this.validationErrorSourceLocators;
993 List<Guid> AcquiredObjectReferences
997 if (this.acquiredObjectReferences == null)
999 this.acquiredObjectReferences = new List<Guid>();
1002 return this.acquiredObjectReferences;
1006 internal AttachedProperty<ValidationState> ValidationStateProperty
1010 return this.validationStateProperty;
1014 internal AttachedProperty<string> ValidationMessageProperty
1018 return this.validationMessageProperty;
1022 class ValidationErrorState
1024 internal ValidationErrorState(List<string> errorMessages, ValidationState validationState)
1026 this.ErrorMessages = errorMessages;
1027 this.ValidationState = validationState;
1030 internal List<string> ErrorMessages { get; set; }
1031 internal ValidationState ValidationState { get; set; }