//----------------------------------------------------------------
// 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")]
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