//---------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. //---------------------------------------------------------------- namespace System.Activities.Statements { using System.Activities; using System.Activities.Expressions; using System.Activities.Persistence; using System.Activities.Tracking; using System.Activities.Validation; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using System.Threading; using System.Transactions; using System.Xml.Linq; using System.Workflow.Runtime; using System.Workflow.ComponentModel.Compiler; using ValidationError = System.Activities.Validation.ValidationError; using System.Workflow.Runtime.Hosting; using System.Workflow.Activities; using System.Runtime.Serialization; [SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "The type name 'Interop' conflicts in whole or in part with the namespace name 'System.Web.Services.Interop' - not common usage")] [Obsolete("The WF3 Types are deprecated. Instead, please use the new WF4 Types from System.Activities.*")] public sealed class Interop : NativeActivity, ICustomTypeDescriptor { static Func getDefaultTimerExtension = new Func(GetDefaultTimerExtension); static Func getInteropPersistenceParticipant = new Func(GetInteropPersistenceParticipant); Dictionary properties; Dictionary metaProperties; System.Workflow.ComponentModel.Activity v1Activity; IList outputPropertyDefinitions; HashSet extraDynamicArguments; bool exposedBodyPropertiesCacheIsValid; IList exposedBodyProperties; Variable interopActivityExecutor; Variable runtimeTransactionHandle; BookmarkCallback onResumeBookmark; CompletionCallback onPersistComplete; BookmarkCallback onTransactionComplete; Type activityType; Persist persistActivity; internal const string InArgumentSuffix = "In"; internal const string OutArgumentSuffix = "Out"; Variable persistOnClose; Variable interopEnlistment; Variable outstandingException; object thisLock; // true if the body type is a valid activity. used so we can have delayed validation support in the designer bool hasValidBody; // true if the V3 activity property names will conflict with our generated argument names bool hasNameCollision; public Interop() : base() { this.interopActivityExecutor = new Variable(); this.runtimeTransactionHandle = new Variable(); this.persistOnClose = new Variable(); this.interopEnlistment = new Variable(); this.outstandingException = new Variable(); this.onResumeBookmark = new BookmarkCallback(this.OnResumeBookmark); this.persistActivity = new Persist(); this.thisLock = new object(); base.Constraints.Add(ProcessAdvancedConstraints()); } [DefaultValue(null)] public Type ActivityType { get { return this.activityType; } set { if (value != this.activityType) { this.hasValidBody = false; if (value != null) { if (typeof(System.Workflow.ComponentModel.Activity).IsAssignableFrom(value) && value.GetConstructor(Type.EmptyTypes) != null) { this.hasValidBody = true; } } this.activityType = value; if (this.metaProperties != null) { this.metaProperties.Clear(); } if (this.outputPropertyDefinitions != null) { this.outputPropertyDefinitions.Clear(); } if (this.properties != null) { this.properties.Clear(); } if (this.exposedBodyProperties != null) { for (int i = 0; i < this.exposedBodyProperties.Count; i++) { this.exposedBodyProperties[i].Invalidate(); } this.exposedBodyProperties.Clear(); } this.exposedBodyPropertiesCacheIsValid = false; this.v1Activity = null; } } } [Browsable(false)] public IDictionary ActivityProperties { get { if (this.properties == null) { this.properties = new Dictionary(); } return this.properties; } } [Browsable(false)] public IDictionary ActivityMetaProperties { get { if (this.metaProperties == null) { this.metaProperties = new Dictionary(); } return this.metaProperties; } } protected override bool CanInduceIdle { get { return true; } } internal System.Workflow.ComponentModel.Activity ComponentModelActivity { get { if (this.v1Activity == null && this.ActivityType != null) { Debug.Assert(this.hasValidBody, "should only be called when we have a valid body"); this.v1Activity = CreateActivity(); } return this.v1Activity; } } internal IList OutputPropertyDefinitions { get { return this.outputPropertyDefinitions; } } internal bool HasNameCollision { get { return this.hasNameCollision; } } protected override void CacheMetadata(NativeActivityMetadata metadata) { if (this.extraDynamicArguments != null) { this.extraDynamicArguments.Clear(); } this.v1Activity = null; if (this.hasValidBody) { //Cache the output properties prop info for look up. this.outputPropertyDefinitions = new List(); //Cache the extra property definitions for look up in OnOpen if (this.properties != null) { if (this.extraDynamicArguments == null) { this.extraDynamicArguments = new HashSet(); } foreach (string name in properties.Keys) { this.extraDynamicArguments.Add(name); } } //Create matched pair of RuntimeArguments for every property: Property (InArgument) & PropertyOut (Argument) PropertyInfo[] bodyProperties = this.ActivityType.GetProperties(); // recheck for name collisions this.hasNameCollision = InteropEnvironment.ParameterHelper.HasPropertyNameCollision(bodyProperties); foreach (PropertyInfo propertyInfo in bodyProperties) { if (InteropEnvironment.ParameterHelper.IsBindable(propertyInfo)) { string propertyInName; //If there are any Property/PropertyOut name pairs already extant, we fall back to renaming the InArgument half of the pair as well if (this.hasNameCollision) { propertyInName = propertyInfo.Name + Interop.InArgumentSuffix; } else { propertyInName = propertyInfo.Name; } //We always rename the OutArgument half of the pair string propertyOutName = propertyInfo.Name + Interop.OutArgumentSuffix; RuntimeArgument inArgument = new RuntimeArgument(propertyInName, propertyInfo.PropertyType, ArgumentDirection.In); RuntimeArgument outArgument = new RuntimeArgument(propertyOutName, propertyInfo.PropertyType, ArgumentDirection.Out); if (this.properties != null) { Argument inBinding = null; if (this.properties.TryGetValue(propertyInName, out inBinding)) { if (inBinding.Direction != ArgumentDirection.In) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.InteropArgumentDirectionMismatch, propertyInName, propertyOutName)); } this.extraDynamicArguments.Remove(propertyInName); metadata.Bind(inBinding, inArgument); } Argument outBinding = null; if (this.properties.TryGetValue(propertyOutName, out outBinding)) { this.extraDynamicArguments.Remove(propertyOutName); metadata.Bind(outBinding, outArgument); } } metadata.AddArgument(inArgument); metadata.AddArgument(outArgument); this.outputPropertyDefinitions.Add(propertyInfo); } } } metadata.SetImplementationVariablesCollection( new Collection { this.interopActivityExecutor, this.runtimeTransactionHandle, this.persistOnClose, this.interopEnlistment, this.outstandingException }); metadata.AddImplementationChild(this.persistActivity); if (!this.hasValidBody) { if (this.ActivityType == null) { metadata.AddValidationError(string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.InteropBodyNotSet, this.DisplayName)); } else { // Body needs to be a WF 3.0 activity if (!typeof(System.Workflow.ComponentModel.Activity).IsAssignableFrom(this.ActivityType)) { metadata.AddValidationError(string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.InteropWrongBody, this.DisplayName)); } // and have a default ctor if (this.ActivityType.GetConstructor(Type.EmptyTypes) == null) { metadata.AddValidationError(string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.InteropBodyMustHavePublicDefaultConstructor, this.DisplayName)); } } } else { if (this.extraDynamicArguments != null && this.extraDynamicArguments.Count > 0) { metadata.AddValidationError(string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.AttemptToBindUnknownProperties, this.DisplayName, this.extraDynamicArguments.First())); } else { try { InitializeMetaProperties(this.ComponentModelActivity); // We call InitializeDefinitionForRuntime in the first call to execute to // make sure it only happens once. } catch (InvalidOperationException e) { metadata.AddValidationError(e.Message); } } } metadata.AddDefaultExtensionProvider(getDefaultTimerExtension); metadata.AddDefaultExtensionProvider(getInteropPersistenceParticipant); } static TimerExtension GetDefaultTimerExtension() { return new DurableTimerExtension(); } static InteropPersistenceParticipant GetInteropPersistenceParticipant() { return new InteropPersistenceParticipant(); } protected override void Execute(NativeActivityContext context) { // WorkflowRuntimeService workflowRuntimeService = context.GetExtension(); if (workflowRuntimeService != null && !(workflowRuntimeService is ExternalDataExchangeService)) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.InteropWorkflowRuntimeServiceNotSupported)); } lock (this.thisLock) { ((System.Workflow.ComponentModel.IDependencyObjectAccessor)this.ComponentModelActivity).InitializeDefinitionForRuntime(null); } if (!this.ComponentModelActivity.Enabled) { return; } System.Workflow.ComponentModel.Activity activityInstance = CreateActivity(); InitializeMetaProperties(activityInstance); activityInstance.SetValue(WorkflowExecutor.WorkflowInstanceIdProperty, context.WorkflowInstanceId); InteropExecutor interopExecutor = new InteropExecutor(context.WorkflowInstanceId, activityInstance, this.OutputPropertyDefinitions, this.ComponentModelActivity); if (!interopExecutor.HasCheckedForTrackingParticipant) { interopExecutor.TrackingEnabled = (context.GetExtension() != null); interopExecutor.HasCheckedForTrackingParticipant = true; } this.interopActivityExecutor.Set(context, interopExecutor); //Register the Handle as an execution property so that we can call GetCurrentTransaction or //RequestTransactionContext on it later RuntimeTransactionHandle runtimeTransactionHandle = this.runtimeTransactionHandle.Get(context); context.Properties.Add(runtimeTransactionHandle.ExecutionPropertyName, runtimeTransactionHandle); try { using (new ServiceEnvironment(activityInstance)) { using (InteropEnvironment interopEnvironment = new InteropEnvironment( interopExecutor, context, this.onResumeBookmark, this, runtimeTransactionHandle.GetCurrentTransaction(context))) { interopEnvironment.Execute(this.ComponentModelActivity, context); } } } catch (Exception exception) { if (WorkflowExecutor.IsIrrecoverableException(exception) || !this.persistOnClose.Get(context)) { throw; } // We are not ----ing the exception. The exception is saved in this.outstandingException. // We will throw the exception from OnPersistComplete. } } protected override void Cancel(NativeActivityContext context) { InteropExecutor interopExecutor = this.interopActivityExecutor.Get(context); if (!interopExecutor.HasCheckedForTrackingParticipant) { interopExecutor.TrackingEnabled = (context.GetExtension() != null); interopExecutor.HasCheckedForTrackingParticipant = true; } interopExecutor.EnsureReload(this); try { using (InteropEnvironment interopEnvironment = new InteropEnvironment( interopExecutor, context, this.onResumeBookmark, this, this.runtimeTransactionHandle.Get(context).GetCurrentTransaction(context))) { interopEnvironment.Cancel(); } } catch (Exception exception) { if (WorkflowExecutor.IsIrrecoverableException(exception) || !this.persistOnClose.Get(context)) { throw; } // We are not ----ing the exception. The exception is saved in this.outstandingException. // We will throw the exception from OnPersistComplete. } } internal void SetOutputArgumentValues(IDictionary outputs, NativeActivityContext context) { if ((this.properties != null) && (outputs != null)) { foreach (KeyValuePair output in outputs) { Argument argument; if (this.properties.TryGetValue(output.Key, out argument) && argument != null) { if (argument.Direction == ArgumentDirection.Out) { argument.Set(context, output.Value); } } } } } internal IDictionary GetInputArgumentValues(NativeActivityContext context) { Dictionary arguments = null; if (this.properties != null) { foreach (KeyValuePair parameter in this.properties) { Argument argument = parameter.Value; if (argument.Direction == ArgumentDirection.In) { if (arguments == null) { arguments = new Dictionary(); } arguments.Add(parameter.Key, argument.Get(context)); } } } return arguments; } System.Workflow.ComponentModel.Activity CreateActivity() { Debug.Assert(this.ActivityType != null, "ActivityType must be set by the time we get here"); System.Workflow.ComponentModel.Activity activity = Activator.CreateInstance(this.ActivityType) as System.Workflow.ComponentModel.Activity; Debug.Assert(activity != null, "We should have validated that the type has a default ctor() and derives from System.Workflow.ComponentModel.Activity."); return activity; } void InitializeMetaProperties(System.Workflow.ComponentModel.Activity activity) { Debug.Assert((activity.GetType() == this.ActivityType), "activity must be the same type as this.ActivityType"); if (this.metaProperties != null && this.metaProperties.Count > 0) { foreach (string name in this.metaProperties.Keys) { PropertyInfo property = this.ActivityType.GetProperty(name); if (property == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.MetaPropertyDoesNotExist, name, this.ActivityType.FullName)); } property.SetValue(activity, this.metaProperties[name], null); } } } void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object state) { InteropExecutor interopExecutor = this.interopActivityExecutor.Get(context); if (!interopExecutor.HasCheckedForTrackingParticipant) { interopExecutor.TrackingEnabled = (context.GetExtension() != null); interopExecutor.HasCheckedForTrackingParticipant = true; } interopExecutor.EnsureReload(this); try { using (InteropEnvironment interopEnvironment = new InteropEnvironment( interopExecutor, context, this.onResumeBookmark, this, this.runtimeTransactionHandle.Get(context).GetCurrentTransaction(context))) { IComparable queueName = interopExecutor.BookmarkQueueMap[bookmark]; interopEnvironment.EnqueueEvent(queueName, state); } } catch (Exception exception) { if (WorkflowExecutor.IsIrrecoverableException(exception) || !this.persistOnClose.Get(context)) { throw; } // We are not ----ing the exception. The exception is saved in this.outstandingException. // We will throw the exception from OnPersistComplete. } } AttributeCollection ICustomTypeDescriptor.GetAttributes() { return TypeDescriptor.GetAttributes(this, true); } string ICustomTypeDescriptor.GetClassName() { return TypeDescriptor.GetClassName(this, true); } string ICustomTypeDescriptor.GetComponentName() { return TypeDescriptor.GetComponentName(this, true); } TypeConverter ICustomTypeDescriptor.GetConverter() { return TypeDescriptor.GetConverter(this, true); } EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() { return TypeDescriptor.GetDefaultEvent(this, true); } PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() { return TypeDescriptor.GetDefaultProperty(this, true); } object ICustomTypeDescriptor.GetEditor(Type editorBaseType) { return TypeDescriptor.GetEditor(this, editorBaseType, true); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) { return TypeDescriptor.GetEvents(this, attributes, true); } EventDescriptorCollection ICustomTypeDescriptor.GetEvents() { return TypeDescriptor.GetEvents(this, true); } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) { List properties = new List(); PropertyDescriptorCollection interopProperties; if (attributes != null) { interopProperties = TypeDescriptor.GetProperties(this, attributes, true); } else { interopProperties = TypeDescriptor.GetProperties(this, true); } for (int i = 0; i < interopProperties.Count; i++) { properties.Add(interopProperties[i]); } if (this.hasValidBody) { // First, cache the full set of body properties if (!this.exposedBodyPropertiesCacheIsValid) { //Create matched pair of RuntimeArguments for every property: Property (InArgument) & PropertyOut (Argument) PropertyInfo[] bodyProperties = this.ActivityType.GetProperties(); // recheck for name collisions this.hasNameCollision = InteropEnvironment.ParameterHelper.HasPropertyNameCollision(bodyProperties); for (int i = 0; i < bodyProperties.Length; i++) { PropertyInfo property = bodyProperties[i]; bool isMetaProperty; if (InteropEnvironment.ParameterHelper.IsBindableOrMetaProperty(property, out isMetaProperty)) { // Propagate the attributes to the PropertyDescriptor, appending a DesignerSerializationVisibility attribute Attribute[] customAttributes = Attribute.GetCustomAttributes(property, true); Attribute[] newAttributes = new Attribute[customAttributes.Length + 1]; customAttributes.CopyTo(newAttributes, 0); newAttributes[customAttributes.Length] = new DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden); if (this.exposedBodyProperties == null) { this.exposedBodyProperties = new List(bodyProperties.Length); } if (isMetaProperty) { InteropProperty descriptor = new LiteralProperty(this, property.Name, property.PropertyType, newAttributes); this.exposedBodyProperties.Add(descriptor); } else { InteropProperty inDescriptor; //If there are any Property/PropertyOut name pairs already extant, we fall back to renaming the InArgument half of the pair as well if (this.hasNameCollision) { inDescriptor = new ArgumentProperty(this, property.Name + InArgumentSuffix, Argument.Create(property.PropertyType, ArgumentDirection.In), newAttributes); } else { inDescriptor = new ArgumentProperty(this, property.Name, Argument.Create(property.PropertyType, ArgumentDirection.In), newAttributes); } this.exposedBodyProperties.Add(inDescriptor); //We always rename the OutArgument half of the pair InteropProperty outDescriptor = new ArgumentProperty(this, property.Name + OutArgumentSuffix, Argument.Create(property.PropertyType, ArgumentDirection.Out), newAttributes); this.exposedBodyProperties.Add(outDescriptor); } } } this.exposedBodyPropertiesCacheIsValid = true; } // Now adds body properties, complying with the filter: if (this.exposedBodyProperties != null) { for (int i = 0; i < this.exposedBodyProperties.Count; i++) { PropertyDescriptor descriptor = this.exposedBodyProperties[i]; if (attributes == null || !ShouldFilterProperty(descriptor, attributes)) { properties.Add(descriptor); } } } } return new PropertyDescriptorCollection(properties.ToArray()); } static bool ShouldFilterProperty(PropertyDescriptor property, Attribute[] attributes) { if (attributes == null || attributes.Length == 0) { return false; } for (int i = 0; i < attributes.Length; i++) { Attribute filterAttribute = attributes[i]; Attribute propertyAttribute = property.Attributes[filterAttribute.GetType()]; if (propertyAttribute == null) { if (!filterAttribute.IsDefaultAttribute()) { return true; } } else { if (!filterAttribute.Match(propertyAttribute)) { return true; } } } return false; } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() { return ((ICustomTypeDescriptor)this).GetProperties(null); } object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) { InteropProperty intProp = pd as InteropProperty; if (intProp != null) { return intProp.Owner; } else { return this; } } internal void OnClose(NativeActivityContext context, Exception exception) { if (this.persistOnClose.Get(context)) { if (exception == null) { context.ScheduleActivity(this.persistActivity); } else { // The V1 workflow faulted and there is an uncaught exception. We cannot throw // the exception right away because we must Persist in order to process the WorkBatch. // So we are saving the uncaught exception and scheduling the Persist activity with a completion callback. // We will throw the exception from OnPersistComplete. this.outstandingException.Set(context, exception); if (this.onPersistComplete == null) { this.onPersistComplete = new CompletionCallback(this.OnPersistComplete); } context.ScheduleActivity(this.persistActivity, this.onPersistComplete); } } this.interopEnlistment.Set(context, null); } internal void Persist(NativeActivityContext context) { if (this.onPersistComplete == null) { this.onPersistComplete = new CompletionCallback(this.OnPersistComplete); } // If Persist fails for any reason, the workflow aborts context.ScheduleActivity(this.persistActivity, this.onPersistComplete); } internal void OnPersistComplete(NativeActivityContext context, ActivityInstance completedInstance) { this.persistOnClose.Set(context, false); Exception exception = this.outstandingException.Get(context); if (exception != null) { this.outstandingException.Set(context, null); throw exception; } this.Resume(context, null); } internal void CreateTransaction(NativeActivityContext context, TransactionOptions txOptions) { RuntimeTransactionHandle transactionHandle = this.runtimeTransactionHandle.Get(context); Debug.Assert(transactionHandle != null, "RuntimeTransactionHandle is null"); transactionHandle.RequestTransactionContext(context, OnTransactionContextAcquired, txOptions); } void OnTransactionContextAcquired(NativeActivityTransactionContext context, object state) { Debug.Assert(context != null, "ActivityTransactionContext was null"); TransactionOptions txOptions = (TransactionOptions)state; CommittableTransaction transaction = new CommittableTransaction(txOptions); context.SetRuntimeTransaction(transaction); this.Resume(context, transaction); } internal void CommitTransaction(NativeActivityContext context) { if (this.onTransactionComplete == null) { this.onTransactionComplete = new BookmarkCallback(this.OnTransactionComplete); } RuntimeTransactionHandle transactionHandle = this.runtimeTransactionHandle.Get(context); transactionHandle.CompleteTransaction(context, this.onTransactionComplete); } void OnTransactionComplete(NativeActivityContext context, Bookmark bookmark, object state) { this.Resume(context, null); } void Resume(NativeActivityContext context, Transaction transaction) { InteropExecutor interopExecutor = this.interopActivityExecutor.Get(context); if (!interopExecutor.HasCheckedForTrackingParticipant) { interopExecutor.TrackingEnabled = (context.GetExtension() != null); interopExecutor.HasCheckedForTrackingParticipant = true; } interopExecutor.EnsureReload(this); try { using (InteropEnvironment interopEnvironment = new InteropEnvironment( interopExecutor, context, this.onResumeBookmark, this, transaction)) { interopEnvironment.Resume(); } } catch (Exception exception) { if (WorkflowExecutor.IsIrrecoverableException(exception) || !this.persistOnClose.Get(context)) { throw; } // We are not ----ing the exception. The exception is saved in this.outstandingException. // We will throw the exception from OnPersistComplete. } } internal void AddResourceManager(NativeActivityContext context, VolatileResourceManager resourceManager) { if (Transaction.Current != null && Transaction.Current.TransactionInformation.Status == TransactionStatus.Active) { InteropEnlistment enlistment = this.interopEnlistment.Get(context); if (enlistment == null || !enlistment.IsValid) { enlistment = new InteropEnlistment(Transaction.Current, resourceManager); Transaction.Current.EnlistVolatile(enlistment, EnlistmentOptions.EnlistDuringPrepareRequired); this.interopEnlistment.Set(context, enlistment); } } else { InteropPersistenceParticipant persistenceParticipant = context.GetExtension(); persistenceParticipant.Add(this.Id, resourceManager); this.persistOnClose.Set(context, true); } } Constraint ProcessAdvancedConstraints() { DelegateInArgument element = new DelegateInArgument() { Name = "element" }; DelegateInArgument validationContext = new DelegateInArgument() { Name = "validationContext" }; DelegateInArgument parent = new DelegateInArgument() { Name = "parent" }; //This will accumulate all potential violations at the root level. See the use case DIRECT of the Interop spec Variable> rootValidationDataVar = new Variable>(context => new HashSet()); //This will accumulate all violations at the nested level. See the use case NESTED of the Interop spec Variable> nestedChildrenValidationDataVar = new Variable>(context => new HashSet()); return new Constraint { Body = new ActivityAction { Argument1 = element, Argument2 = validationContext, Handler = new If { Condition = new InArgument(env => element.Get(env).hasValidBody), Then = new Sequence { Variables = { rootValidationDataVar, nestedChildrenValidationDataVar }, Activities = { //First traverse the interop body and collect all available data for validation. This is done at all levels, DIRECT and NESTED new WalkInteropBodyAndGatherData() { RootLevelValidationData = new InArgument>(rootValidationDataVar), NestedChildrenValidationData = new InArgument>(nestedChildrenValidationDataVar), InteropActivity = element }, //This is based off the table in the Interop spec. new ValidateAtRootAndNestedLevels() { RootLevelValidationData = rootValidationDataVar, NestedChildrenValidationData = nestedChildrenValidationDataVar, Interop = element, }, //Traverse the parent chain of the Interop activity to look for specifc violations regarding composition of 3.0 activities within 4.0 activities. //Specifically, // - 3.0 TransactionScope within a 4.0 TransactionScope // - 3.0 PersistOnClose within a 4.0 TransactionScope // new ForEach { Values = new GetParentChain { ValidationContext = validationContext, }, Body = new ActivityAction { Argument = parent, Handler = new Sequence { Activities = { new If() { Condition = new Or { Left = new Equal { Left = new ObtainType { Input = parent, }, Right = new InArgument(context => typeof(System.Activities.Statements.TransactionScope)) }, Right = new Equal { Left = new InArgument(env => parent.Get(env).GetType().FullName), Right = "System.ServiceModel.Activities.TransactedReceiveScope" } }, Then = new Sequence { Activities = { new AssertValidation { //Here we only pass the NestedChildrenValidationData since root level use //of TransactionScope would have already been flagged as an error Assertion = new CheckForTransactionScope() { ValidationResults = nestedChildrenValidationDataVar }, Message = new InArgument(ExecutionStringManager.InteropBodyNestedTransactionScope) }, new AssertValidation { Assertion = new CheckForPersistOnClose() { NestedChildrenValidationData = nestedChildrenValidationDataVar, RootLevelValidationData = rootValidationDataVar }, Message = new InArgument(ExecutionStringManager.InteropBodyNestedPersistOnCloseWithinTransactionScope) }, } }, }, } } } }, new ActivityTreeValidation() { Interop = element } } } } } }; } class ActivityTreeValidation : NativeActivity { public ActivityTreeValidation() { } public InArgument Interop { get; set; } protected override void Execute(NativeActivityContext context) { Interop interop = this.Interop.Get(context); if (interop == null) { return; } if (!typeof(System.Workflow.ComponentModel.Activity).IsAssignableFrom(interop.ActivityType)) { return; } System.ComponentModel.Design.ServiceContainer container = new System.ComponentModel.Design.ServiceContainer(); container.AddService(typeof(ITypeProvider), CreateTypeProvider(interop.ActivityType)); ValidationManager manager = new ValidationManager(container); System.Workflow.ComponentModel.Activity interopBody = interop.ComponentModelActivity; using (WorkflowCompilationContext.CreateScope(manager)) { foreach (Validator validator in manager.GetValidators(interop.ActivityType)) { ValidationErrorCollection errors = validator.Validate(manager, interopBody); foreach (System.Workflow.ComponentModel.Compiler.ValidationError error in errors) { Constraint.AddValidationError(context, new ValidationError(error.ErrorText, error.IsWarning, error.PropertyName)); } } } } static TypeProvider CreateTypeProvider(Type rootType) { TypeProvider typeProvider = new TypeProvider(null); typeProvider.SetLocalAssembly(rootType.Assembly); typeProvider.AddAssembly(rootType.Assembly); foreach (AssemblyName assemblyName in rootType.Assembly.GetReferencedAssemblies()) { Assembly referencedAssembly = null; try { referencedAssembly = Assembly.Load(assemblyName); if (referencedAssembly != null) typeProvider.AddAssembly(referencedAssembly); } catch { } if (referencedAssembly == null && assemblyName.CodeBase != null) typeProvider.AddAssemblyReference(assemblyName.CodeBase); } return typeProvider; } } class CheckForTransactionScope : CodeActivity { public InArgument> ValidationResults { get; set; } protected override bool Execute(CodeActivityContext context) { HashSet validationResults = this.ValidationResults.Get(context); if (validationResults.Contains(InteropValidationEnum.TransactionScope)) { return false; } return true; } } class CheckForPersistOnClose : CodeActivity { public InArgument> NestedChildrenValidationData { get; set; } public InArgument> RootLevelValidationData { get; set; } protected override bool Execute(CodeActivityContext context) { HashSet nestedValidationData = this.NestedChildrenValidationData.Get(context); HashSet rootValidationData = this.RootLevelValidationData.Get(context); if (nestedValidationData.Contains(InteropValidationEnum.PersistOnClose) || rootValidationData.Contains(InteropValidationEnum.PersistOnClose)) { return false; } return true; } } class ValidateAtRootAndNestedLevels : NativeActivity { public ValidateAtRootAndNestedLevels() { } public InArgument Interop { get; set; } public InArgument> RootLevelValidationData { get; set; } public InArgument> NestedChildrenValidationData { get; set; } protected override void Execute(NativeActivityContext context) { Interop activity = this.Interop.Get(context); foreach (InteropValidationEnum validationEnum in this.RootLevelValidationData.Get(context)) { //We care to mark PersistOnClose during the walking algorithm because we need to check if it happens under a 4.0 TransactionScopActivity and flag that //That is done later, so skip here. if (validationEnum != InteropValidationEnum.PersistOnClose) { string message = string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.InteropBodyRootLevelViolation, activity.DisplayName, validationEnum.ToString() + "Activity"); Constraint.AddValidationError(context, new ValidationError(message)); } } foreach (InteropValidationEnum validationEnum in this.NestedChildrenValidationData.Get(context)) { //We care to mark PersistOnClose or TransactionScope during the walking algorithm because we need to check if it happens under a 4.0 TransactionScopActivity and flag that //That is done later, so skip here. if ((validationEnum != InteropValidationEnum.PersistOnClose) && (validationEnum != InteropValidationEnum.TransactionScope)) { string message = string.Format(CultureInfo.CurrentCulture, ExecutionStringManager.InteropBodyNestedViolation, activity.DisplayName, validationEnum.ToString() + "Activity"); Constraint.AddValidationError(context, new ValidationError(message)); } } } } class WalkInteropBodyAndGatherData : System.Activities.CodeActivity { public InArgument InteropActivity { get; set; } public InArgument> RootLevelValidationData { get; set; } public InArgument> NestedChildrenValidationData { get; set; } protected override void Execute(CodeActivityContext context) { Interop interop = this.InteropActivity.Get(context); Debug.Assert(interop != null, "Interop activity is null"); Debug.Assert(interop.hasValidBody, "Interop activity has an invalid body"); System.Workflow.ComponentModel.Activity interopBody = interop.ComponentModelActivity; Debug.Assert(interopBody != null, "Interop Body was null"); HashSet validationResults; validationResults = this.RootLevelValidationData.Get(context); Debug.Assert(validationResults != null, "The RootLevelValidationData hash set was null"); //Gather data at root level first ProcessAtRootLevel(interopBody, validationResults); validationResults = null; validationResults = this.NestedChildrenValidationData.Get(context); Debug.Assert(validationResults != null, "The NestedChildrenValidationData hash set was null"); //Next, process nested children of the Body if (interopBody is System.Workflow.ComponentModel.CompositeActivity) { ProcessNestedChildren(interopBody, validationResults); } return; } void ProcessAtRootLevel(System.Workflow.ComponentModel.Activity interopBody, HashSet validationResults) { Debug.Assert(interopBody != null, "Interop Body is null"); Debug.Assert(validationResults != null, "The HashSet of validation results is null"); if (interopBody.PersistOnClose) { validationResults.Add(InteropValidationEnum.PersistOnClose); } Type interopBodyType = interopBody.GetType(); if (interopBodyType == typeof(System.Workflow.ComponentModel.TransactionScopeActivity)) { validationResults.Add(InteropValidationEnum.TransactionScope); } else if (interopBodyType == typeof(System.Workflow.Activities.CodeActivity)) { validationResults.Add(InteropValidationEnum.Code); } else if (interopBodyType == typeof(System.Workflow.Activities.DelayActivity)) { validationResults.Add(InteropValidationEnum.Delay); } else if (interopBodyType == typeof(System.Workflow.Activities.InvokeWebServiceActivity)) { validationResults.Add(InteropValidationEnum.InvokeWebService); } else if (interopBodyType == typeof(System.Workflow.Activities.InvokeWorkflowActivity)) { validationResults.Add(InteropValidationEnum.InvokeWorkflow); } else if (interopBodyType == typeof(System.Workflow.Activities.PolicyActivity)) { validationResults.Add(InteropValidationEnum.Policy); } else if (interopBodyType.FullName == "System.Workflow.Activities.SendActivity") { validationResults.Add(InteropValidationEnum.Send); } else if (interopBodyType == typeof(System.Workflow.Activities.SetStateActivity)) { validationResults.Add(InteropValidationEnum.SetState); } else if (interopBodyType == typeof(System.Workflow.Activities.WebServiceFaultActivity)) { validationResults.Add(InteropValidationEnum.WebServiceFault); } else if (interopBodyType == typeof(System.Workflow.Activities.WebServiceInputActivity)) { validationResults.Add(InteropValidationEnum.WebServiceInput); } else if (interopBodyType == typeof(System.Workflow.Activities.WebServiceOutputActivity)) { validationResults.Add(InteropValidationEnum.WebServiceOutput); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.CompensateActivity)) { validationResults.Add(InteropValidationEnum.Compensate); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.SuspendActivity)) { validationResults.Add(InteropValidationEnum.Suspend); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.TerminateActivity)) { validationResults.Add(InteropValidationEnum.Terminate); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.ThrowActivity)) { validationResults.Add(InteropValidationEnum.Throw); } else if (interopBodyType == typeof(System.Workflow.Activities.ConditionedActivityGroup)) { validationResults.Add(InteropValidationEnum.ConditionedActivityGroup); } else if (interopBodyType == typeof(System.Workflow.Activities.EventHandlersActivity)) { validationResults.Add(InteropValidationEnum.EventHandlers); } else if (interopBodyType == typeof(System.Workflow.Activities.EventHandlingScopeActivity)) { validationResults.Add(InteropValidationEnum.EventHandlingScope); } else if (interopBodyType == typeof(System.Workflow.Activities.IfElseActivity)) { validationResults.Add(InteropValidationEnum.IfElse); } else if (interopBodyType == typeof(System.Workflow.Activities.ListenActivity)) { validationResults.Add(InteropValidationEnum.Listen); } else if (interopBodyType == typeof(System.Workflow.Activities.ParallelActivity)) { validationResults.Add(InteropValidationEnum.Parallel); } else if (interopBodyType == typeof(System.Workflow.Activities.ReplicatorActivity)) { validationResults.Add(InteropValidationEnum.Replicator); } else if (interopBodyType == typeof(System.Workflow.Activities.SequenceActivity)) { validationResults.Add(InteropValidationEnum.Sequence); } else if (interopBodyType == typeof(System.Workflow.Activities.CompensatableSequenceActivity)) { validationResults.Add(InteropValidationEnum.CompensatableSequence); } else if (interopBodyType == typeof(System.Workflow.Activities.EventDrivenActivity)) { validationResults.Add(InteropValidationEnum.EventDriven); } else if (interopBodyType == typeof(System.Workflow.Activities.IfElseBranchActivity)) { validationResults.Add(InteropValidationEnum.IfElseBranch); } else if (interopBodyType.FullName == "System.Workflow.Activities.ReceiveActivity") { validationResults.Add(InteropValidationEnum.Receive); } else if (interopBodyType == typeof(System.Workflow.Activities.SequentialWorkflowActivity)) { validationResults.Add(InteropValidationEnum.SequentialWorkflow); } else if (interopBodyType == typeof(System.Workflow.Activities.StateFinalizationActivity)) { validationResults.Add(InteropValidationEnum.StateFinalization); } else if (interopBodyType == typeof(System.Workflow.Activities.StateInitializationActivity)) { validationResults.Add(InteropValidationEnum.StateInitialization); } else if (interopBodyType == typeof(System.Workflow.Activities.StateActivity)) { validationResults.Add(InteropValidationEnum.State); } else if (interopBodyType == typeof(System.Workflow.Activities.StateMachineWorkflowActivity)) { validationResults.Add(InteropValidationEnum.StateMachineWorkflow); } else if (interopBodyType == typeof(System.Workflow.Activities.WhileActivity)) { validationResults.Add(InteropValidationEnum.While); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.CancellationHandlerActivity)) { validationResults.Add(InteropValidationEnum.CancellationHandler); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.CompensatableTransactionScopeActivity)) { validationResults.Add(InteropValidationEnum.CompensatableTransactionScope); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.CompensationHandlerActivity)) { validationResults.Add(InteropValidationEnum.CompensationHandler); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.FaultHandlerActivity)) { validationResults.Add(InteropValidationEnum.FaultHandler); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.FaultHandlersActivity)) { validationResults.Add(InteropValidationEnum.FaultHandlers); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.SynchronizationScopeActivity)) { validationResults.Add(InteropValidationEnum.SynchronizationScope); } else if (interopBodyType == typeof(System.Workflow.ComponentModel.ICompensatableActivity)) { validationResults.Add(InteropValidationEnum.ICompensatable); } } void ProcessNestedChildren(System.Workflow.ComponentModel.Activity interopBody, HashSet validationResults) { Debug.Assert(interopBody != null, "Interop Body is null"); Debug.Assert(validationResults != null, "The HashSet of validation results is null"); bool persistOnClose = false; foreach (System.Workflow.ComponentModel.Activity activity in interopBody.CollectNestedActivities()) { if (activity.PersistOnClose) { persistOnClose = true; } if (activity is System.Workflow.ComponentModel.TransactionScopeActivity) { validationResults.Add(InteropValidationEnum.TransactionScope); } else if (activity is System.Workflow.Activities.InvokeWorkflowActivity) { validationResults.Add(InteropValidationEnum.InvokeWorkflow); } // SendActivity is sealed else if (activity.GetType().FullName == "System.Workflow.Activities.SendActivity") { validationResults.Add(InteropValidationEnum.Send); } else if (activity is System.Workflow.Activities.WebServiceFaultActivity) { validationResults.Add(InteropValidationEnum.WebServiceFault); } else if (activity is System.Workflow.Activities.WebServiceInputActivity) { validationResults.Add(InteropValidationEnum.WebServiceInput); } else if (activity is System.Workflow.Activities.WebServiceOutputActivity) { validationResults.Add(InteropValidationEnum.WebServiceOutput); } else if (activity is System.Workflow.ComponentModel.CompensateActivity) { validationResults.Add(InteropValidationEnum.Compensate); } else if (activity is System.Workflow.ComponentModel.SuspendActivity) { validationResults.Add(InteropValidationEnum.Suspend); } else if (activity is System.Workflow.Activities.CompensatableSequenceActivity) { validationResults.Add(InteropValidationEnum.CompensatableSequence); } // ReceiveActivity is sealed else if (activity.GetType().FullName == "System.Workflow.Activities.ReceiveActivity") { validationResults.Add(InteropValidationEnum.Receive); } else if (activity is System.Workflow.ComponentModel.CompensatableTransactionScopeActivity) { validationResults.Add(InteropValidationEnum.CompensatableTransactionScope); } else if (activity is System.Workflow.ComponentModel.CompensationHandlerActivity) { validationResults.Add(InteropValidationEnum.CompensationHandler); } else if (activity is System.Workflow.ComponentModel.ICompensatableActivity) { validationResults.Add(InteropValidationEnum.ICompensatable); } } if (persistOnClose) { validationResults.Add(InteropValidationEnum.PersistOnClose); } } } //This needs to be in [....] with the table in the spec //We use this internally to keep a hashset of validation data enum InteropValidationEnum { Code, Delay, InvokeWebService, InvokeWorkflow, Policy, Send, SetState, WebServiceFault, WebServiceInput, WebServiceOutput, Compensate, Suspend, ConditionedActivityGroup, EventHandlers, EventHandlingScope, IfElse, Listen, Parallel, Replicator, Sequence, CompensatableSequence, EventDriven, IfElseBranch, Receive, SequentialWorkflow, StateFinalization, StateInitialization, State, StateMachineWorkflow, While, CancellationHandler, CompensatableTransactionScope, CompensationHandler, FaultHandler, FaultHandlers, SynchronizationScope, TransactionScope, ICompensatable, PersistOnClose, Terminate, Throw } class ObtainType : CodeActivity { public ObtainType() { } public InArgument Input { get; set; } protected override Type Execute(CodeActivityContext context) { return this.Input.Get(context).GetType(); } } abstract class InteropProperty : PropertyDescriptor { Interop owner; bool isValid; public InteropProperty(Interop owner, string name, Attribute[] propertyInfoAttributes) : base(name, propertyInfoAttributes) { this.owner = owner; this.isValid = true; } public override Type ComponentType { get { ThrowIfInvalid(); return this.owner.GetType(); } } protected internal Interop Owner { get { return this.owner; } } public override bool CanResetValue(object component) { ThrowIfInvalid(); return false; } public override void ResetValue(object component) { ThrowIfInvalid(); } public override bool ShouldSerializeValue(object component) { ThrowIfInvalid(); return false; } protected void ThrowIfInvalid() { if (!this.isValid) { throw new InvalidOperationException(ExecutionStringManager.InteropInvalidPropertyDescriptor); } } internal void Invalidate() { this.isValid = false; } } class ArgumentProperty : InteropProperty { string argumentName; Argument argument; public ArgumentProperty(Interop owner, string argumentName, Argument argument, Attribute[] attributes) : base(owner, argumentName, attributes) { this.argumentName = argumentName; this.argument = argument; } public override bool IsReadOnly { get { ThrowIfInvalid(); return false; } } public override Type PropertyType { get { ThrowIfInvalid(); return GetArgument().GetType(); } } public override object GetValue(object component) { ThrowIfInvalid(); return GetArgument(); } public override void SetValue(object component, object value) { ThrowIfInvalid(); if (value != null) { this.Owner.ActivityProperties[this.argumentName] = (Argument)value; } else { this.Owner.ActivityProperties.Remove(this.argumentName); } } Argument GetArgument() { Argument argument; if (!this.Owner.ActivityProperties.TryGetValue(this.argumentName, out argument)) { argument = this.argument; } return argument; } } class LiteralProperty : InteropProperty { string literalName; Type literalType; public LiteralProperty(Interop owner, string literalName, Type literalType, Attribute[] attributes) : base(owner, literalName, attributes) { this.literalName = literalName; this.literalType = literalType; } public override bool IsReadOnly { get { ThrowIfInvalid(); return false; } } public override Type PropertyType { get { ThrowIfInvalid(); return this.literalType; } } public override object GetValue(object component) { ThrowIfInvalid(); return GetLiteral(); } public override void SetValue(object component, object value) { ThrowIfInvalid(); this.Owner.ActivityMetaProperties[this.literalName] = value; } object GetLiteral() { object literal; if (this.Owner.ActivityMetaProperties.TryGetValue(this.literalName, out literal)) { return literal; } else { return null; } } } class InteropPersistenceParticipant : PersistenceIOParticipant { public InteropPersistenceParticipant() : base(true, false) { this.ResourceManagers = new Dictionary(); this.CommittedResourceManagers = new Dictionary>(); } Dictionary ResourceManagers { get; set; } Dictionary> CommittedResourceManagers { get; set; } protected override IAsyncResult BeginOnSave(IDictionary readWriteValues, IDictionary writeOnlyValues, TimeSpan timeout, AsyncCallback callback, object state) { try { foreach (VolatileResourceManager rm in this.ResourceManagers.Values) { rm.Commit(); } } finally { this.CommittedResourceManagers.Add(Transaction.Current, this.ResourceManagers); this.ResourceManagers = new Dictionary(); Transaction.Current.TransactionCompleted += new TransactionCompletedEventHandler(Current_TransactionCompleted); } return new CompletedAsyncResult(callback, state); } protected override void EndOnSave(IAsyncResult result) { CompletedAsyncResult.End(result); } void Current_TransactionCompleted(object sender, TransactionEventArgs e) { if (e.Transaction.TransactionInformation.Status == TransactionStatus.Committed) { foreach (VolatileResourceManager rm in this.CommittedResourceManagers[e.Transaction].Values) { rm.Complete(); } } else { foreach (VolatileResourceManager rm in this.CommittedResourceManagers[e.Transaction].Values) { rm.ClearAllBatchedWork(); } } this.CommittedResourceManagers.Remove(e.Transaction); } protected override void Abort() { foreach (VolatileResourceManager rm in this.ResourceManagers.Values) { rm.ClearAllBatchedWork(); } this.ResourceManagers = new Dictionary(); } internal void Add(string activityId, VolatileResourceManager rm) { // Add and OnSave shouldn't be called at the same time. A lock isn't needed here. this.ResourceManagers.Add(activityId, rm); } } [DataContract] class InteropEnlistment : IEnlistmentNotification { VolatileResourceManager resourceManager; Transaction transaction; public InteropEnlistment() { } public InteropEnlistment(Transaction transaction, VolatileResourceManager resourceManager) { this.resourceManager = resourceManager; this.transaction = transaction; this.IsValid = true; } public bool IsValid { get; set; } public void Commit(Enlistment enlistment) { this.resourceManager.Complete(); enlistment.Done(); } public void InDoubt(Enlistment enlistment) { // Following the WF3 runtime behavior - Aborting during InDoubt this.Rollback(enlistment); } public void Prepare(PreparingEnlistment preparingEnlistment) { using (System.Transactions.TransactionScope ts = new System.Transactions.TransactionScope(this.transaction)) { this.resourceManager.Commit(); ts.Complete(); } preparingEnlistment.Prepared(); } public void Rollback(Enlistment enlistment) { this.resourceManager.ClearAllBatchedWork(); enlistment.Done(); } } class CompletedAsyncResult : IAsyncResult { AsyncCallback callback; bool endCalled; ManualResetEvent manualResetEvent; object state; object thisLock; public CompletedAsyncResult(AsyncCallback callback, object state) { this.callback = callback; this.state = state; this.thisLock = new object(); if (callback != null) { try { callback(this); } catch (Exception e) // transfer to another thread, this is a fatal situation { throw new InvalidProgramException(ExecutionStringManager.AsyncCallbackThrewException, e); } } } public static void End(IAsyncResult result) { if (result == null) { throw new ArgumentNullException("result"); } CompletedAsyncResult asyncResult = result as CompletedAsyncResult; if (asyncResult == null) { throw new ArgumentException(ExecutionStringManager.InvalidAsyncResult, "result"); } if (asyncResult.endCalled) { throw new InvalidOperationException(ExecutionStringManager.EndCalledTwice); } asyncResult.endCalled = true; if (asyncResult.manualResetEvent != null) { asyncResult.manualResetEvent.Close(); } } public object AsyncState { get { return state; } } public WaitHandle AsyncWaitHandle { get { if (this.manualResetEvent != null) { return this.manualResetEvent; } lock (ThisLock) { if (this.manualResetEvent == null) { this.manualResetEvent = new ManualResetEvent(true); } } return this.manualResetEvent; } } public bool CompletedSynchronously { get { return true; } } public bool IsCompleted { get { return true; } } object ThisLock { get { return this.thisLock; } } } } }