//----------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- namespace System.Activities { using System; using System.Activities.Hosting; using System.Activities.Runtime; using System.Collections.Generic; using System.ComponentModel; using System.Runtime; [Fx.Tag.XamlVisible(false)] public sealed class WorkflowDataContext : CustomTypeDescriptor, INotifyPropertyChanged, IDisposable { ActivityExecutor executor; ActivityInstance activityInstance; IDictionary locationMapping; PropertyChangedEventHandler propertyChangedEventHandler; PropertyDescriptorCollection properties; ActivityContext cachedResolutionContext; internal WorkflowDataContext(ActivityExecutor executor, ActivityInstance activityInstance, bool includeLocalVariables) { this.executor = executor; this.activityInstance = activityInstance; this.IncludesLocalVariables = includeLocalVariables; this.properties = CreateProperties(); } internal bool IncludesLocalVariables { get; set; } public event PropertyChangedEventHandler PropertyChanged; // We want our own cached ActivityContext rather than using this.executor.GetResolutionContext // because there is no synchronization of access to the executor's cached object and access thru // this WorkflowDataContext will not be done on the workflow runtime thread. ActivityContext ResolutionContext { get { ThrowIfEnvironmentDisposed(); if (this.cachedResolutionContext == null) { this.cachedResolutionContext = new ActivityContext(this.activityInstance, this.executor); this.cachedResolutionContext.AllowChainedEnvironmentAccess = true; } else { this.cachedResolutionContext.Reinitialize(this.activityInstance, this.executor); } return this.cachedResolutionContext; } } PropertyChangedEventHandler PropertyChangedEventHandler { get { if (this.propertyChangedEventHandler == null) { this.propertyChangedEventHandler = new PropertyChangedEventHandler(this.OnLocationChanged); } return this.propertyChangedEventHandler; } } PropertyDescriptorCollection CreateProperties() { // The name in child Activity will shadow the name in parent. Dictionary names = new Dictionary(); List propertyList = new List(); LocationReferenceEnvironment environment = this.activityInstance.Activity.PublicEnvironment; bool isLocalEnvironment = true; while (environment != null) { foreach (LocationReference locRef in environment.GetLocationReferences()) { if (this.IncludesLocalVariables || !isLocalEnvironment || !(locRef is Variable)) { AddProperty(locRef, names, propertyList); } } environment = environment.Parent; isLocalEnvironment = false; } return new PropertyDescriptorCollection(propertyList.ToArray(), true); } void AddProperty(LocationReference reference, Dictionary names, List propertyList) { if (!string.IsNullOrEmpty(reference.Name) && !names.ContainsKey(reference.Name)) { names.Add(reference.Name, reference); PropertyDescriptorImpl property = new PropertyDescriptorImpl(reference); propertyList.Add(property); AddNotifyHandler(property); } } void AddNotifyHandler(PropertyDescriptorImpl property) { ActivityContext activityContext = this.ResolutionContext; try { Location location = property.LocationReference.GetLocation(activityContext); INotifyPropertyChanged notify = location as INotifyPropertyChanged; if (notify != null) { notify.PropertyChanged += PropertyChangedEventHandler; if (this.locationMapping == null) { this.locationMapping = new Dictionary(); } this.locationMapping.Add(location, property); } } finally { activityContext.Dispose(); } } void OnLocationChanged(object sender, PropertyChangedEventArgs e) { PropertyChangedEventHandler handler = this.PropertyChanged; if (handler != null) { Location location = (Location)sender; Fx.Assert(this.locationMapping != null, "Location mapping must not be null."); PropertyDescriptorImpl property; if (this.locationMapping.TryGetValue(location, out property)) { if (e.PropertyName == "Value") { handler(this, new PropertyChangedEventArgs(property.Name)); } else { handler(this, new PropertyChangedEventArgs(property.Name + "." + e.PropertyName)); } } } } public void Dispose() { if (this.locationMapping != null) { foreach (KeyValuePair pair in this.locationMapping) { INotifyPropertyChanged notify = pair.Key as INotifyPropertyChanged; if (notify != null) { notify.PropertyChanged -= PropertyChangedEventHandler; } } } } // We need a separate method here from Dispose(), because Dispose currently // doesn't make the WDC uncallable, it just unhooks it from notifications. internal void DisposeEnvironment() { this.activityInstance = null; } void ThrowIfEnvironmentDisposed() { if (this.activityInstance == null) { throw FxTrace.Exception.AsError( new ObjectDisposedException(this.GetType().FullName, SR.WDCDisposed)); } } public override PropertyDescriptorCollection GetProperties() { return this.properties; } class PropertyDescriptorImpl : PropertyDescriptor { LocationReference reference; // public PropertyDescriptorImpl(LocationReference reference) : base(reference.Name, new Attribute[0]) { this.reference = reference; } public override Type ComponentType { get { return typeof(WorkflowDataContext); } } public override bool IsReadOnly { get { // return false; } } public override Type PropertyType { get { return this.reference.Type; } } public LocationReference LocationReference { get { return this.reference; } } public override bool CanResetValue(object component) { return false; } public override object GetValue(object component) { WorkflowDataContext dataContext = (WorkflowDataContext)component; ActivityContext activityContext = dataContext.ResolutionContext; try { return this.reference.GetLocation(activityContext).Value; } finally { activityContext.Dispose(); } } public override void ResetValue(object component) { throw FxTrace.Exception.AsError(new NotSupportedException(SR.CannotResetPropertyInDataContext)); } public override void SetValue(object component, object value) { if (IsReadOnly) { throw FxTrace.Exception.AsError(new NotSupportedException(SR.PropertyReadOnlyInWorkflowDataContext(this.Name))); } WorkflowDataContext dataContext = (WorkflowDataContext)component; ActivityContext activityContext = dataContext.ResolutionContext; try { Location location = this.reference.GetLocation(activityContext); location.Value = value; } finally { activityContext.Dispose(); } } public override bool ShouldSerializeValue(object component) { return true; } } } }