e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
454 lines
21 KiB
C#
454 lines
21 KiB
C#
//-----------------------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace System.ServiceModel.Activities
|
|
{
|
|
using System;
|
|
using System.Activities;
|
|
using System.Activities.Expressions;
|
|
using System.Activities.Statements;
|
|
using System.Activities.Validation;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Runtime;
|
|
using System.Runtime.Collections;
|
|
using System.Windows.Markup;
|
|
using SR2 = System.ServiceModel.Activities.SR;
|
|
|
|
[ContentProperty("Body")]
|
|
public sealed class TransactedReceiveScope : NativeActivity
|
|
{
|
|
Variable<RuntimeTransactionHandle> transactionHandle;
|
|
Collection<Variable> variables;
|
|
const string AbortInstanceOnTransactionFailurePropertyName = "AbortInstanceOnTransactionFailure";
|
|
const string RequestPropertyName = "Request";
|
|
const string BodyPropertyName = "Body";
|
|
Variable<bool> isNested;
|
|
static AsyncCallback transactionCommitCallback;
|
|
|
|
public TransactedReceiveScope()
|
|
{
|
|
this.transactionHandle = new Variable<RuntimeTransactionHandle>
|
|
{
|
|
Name = "TransactionHandle"
|
|
};
|
|
this.isNested = new Variable<bool>();
|
|
|
|
base.Constraints.Add(ProcessChildSubtreeConstraints());
|
|
}
|
|
|
|
[DefaultValue(null)]
|
|
public Receive Request
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
[DefaultValue(null)]
|
|
public Activity Body
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public Collection<Variable> Variables
|
|
{
|
|
get
|
|
{
|
|
if (this.variables == null)
|
|
{
|
|
this.variables = new ValidatingCollection<Variable>
|
|
{
|
|
// disallow null values
|
|
OnAddValidationCallback = item =>
|
|
{
|
|
if (item == null)
|
|
{
|
|
throw FxTrace.Exception.ArgumentNull("item");
|
|
}
|
|
}
|
|
};
|
|
}
|
|
return this.variables;
|
|
}
|
|
}
|
|
|
|
internal static AsyncCallback TransactionCommitAsyncCallback
|
|
{
|
|
get
|
|
{
|
|
if (transactionCommitCallback == null)
|
|
{
|
|
transactionCommitCallback = Fx.ThunkCallback(new AsyncCallback(TransactionCommitCallback));
|
|
}
|
|
|
|
return transactionCommitCallback;
|
|
}
|
|
}
|
|
|
|
Constraint ProcessChildSubtreeConstraints()
|
|
{
|
|
DelegateInArgument<TransactedReceiveScope> element = new DelegateInArgument<TransactedReceiveScope> { Name = "element" };
|
|
DelegateInArgument<ValidationContext> validationContext = new DelegateInArgument<ValidationContext> { Name = "validationContext" };
|
|
DelegateInArgument<Activity> child = new DelegateInArgument<Activity> { Name = "child" };
|
|
Variable<bool> nestedCompensableActivity = new Variable<bool>
|
|
{
|
|
Name = "nestedCompensableActivity"
|
|
};
|
|
|
|
return new Constraint<TransactedReceiveScope>
|
|
{
|
|
Body = new ActivityAction<TransactedReceiveScope, ValidationContext>
|
|
{
|
|
Argument1 = element,
|
|
Argument2 = validationContext,
|
|
Handler = new Sequence
|
|
{
|
|
Variables = { nestedCompensableActivity },
|
|
Activities =
|
|
{
|
|
new ForEach<Activity>
|
|
{
|
|
Values = new GetChildSubtree
|
|
{
|
|
ValidationContext = validationContext,
|
|
},
|
|
Body = new ActivityAction<Activity>
|
|
{
|
|
Argument = child,
|
|
Handler = new Sequence
|
|
{
|
|
Activities =
|
|
{
|
|
new If()
|
|
{
|
|
Condition = new Equal<Type, Type, bool>
|
|
{
|
|
Left = new ObtainType
|
|
{
|
|
Input = new InArgument<Activity>(child)
|
|
},
|
|
Right = new InArgument<Type>(context => typeof(TransactionScope))
|
|
},
|
|
Then = new AssertValidation
|
|
{
|
|
IsWarning = true,
|
|
Assertion = new NestedChildTransactionScopeActivityAbortInstanceFlagValidator
|
|
{
|
|
Child = child
|
|
},
|
|
//Message = new InArgument<string>(env => SR.AbortInstanceOnTransactionFailureDoesNotMatch(child.Get(env).DisplayName, this.DisplayName)),
|
|
Message = new InArgument<string>
|
|
{
|
|
Expression = new NestedChildTransactionScopeActivityAbortInstanceFlagValidatorMessage
|
|
{
|
|
Child = child,
|
|
ParentDisplayName = this.DisplayName
|
|
}
|
|
},
|
|
PropertyName = AbortInstanceOnTransactionFailurePropertyName
|
|
}
|
|
},
|
|
new If()
|
|
{
|
|
Condition = new Equal<Type, Type, bool>
|
|
{
|
|
Left = new ObtainType
|
|
{
|
|
Input = new InArgument<Activity>(child)
|
|
},
|
|
Right = new InArgument<Type>(context => typeof(CompensableActivity))
|
|
},
|
|
Then = new Assign<bool>
|
|
{
|
|
To = new OutArgument<bool>(nestedCompensableActivity),
|
|
Value = new InArgument<bool>(true)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
new AssertValidation
|
|
{
|
|
//Assertion = new InArgument<bool>(env => !nestedCompensableActivity.Get(env)),
|
|
Assertion = new InArgument<bool>
|
|
{
|
|
Expression = new Not<bool, bool>
|
|
{
|
|
Operand = new VariableValue<bool>
|
|
{
|
|
Variable = nestedCompensableActivity
|
|
}
|
|
}
|
|
},
|
|
Message = new InArgument<string>(SR2.CompensableActivityInsideTransactedReceiveScope),
|
|
PropertyName = BodyPropertyName
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
protected override void CacheMetadata(NativeActivityMetadata metadata)
|
|
{
|
|
if (this.Request == null)
|
|
{
|
|
metadata.AddValidationError(new ValidationError(SR2.TransactedReceiveScopeMustHaveValidReceive(this.DisplayName), false, RequestPropertyName));
|
|
}
|
|
metadata.AddChild(this.Request);
|
|
metadata.AddChild(this.Body);
|
|
metadata.SetVariablesCollection(this.Variables);
|
|
metadata.AddImplementationVariable(this.transactionHandle);
|
|
metadata.AddImplementationVariable(this.isNested);
|
|
}
|
|
|
|
protected override void Execute(NativeActivityContext context)
|
|
{
|
|
if (this.Request == null)
|
|
{
|
|
throw FxTrace.Exception.AsError(new ValidationException(SR2.TransactedReceiveScopeRequiresReceive(this.DisplayName)));
|
|
}
|
|
// we have to do this in code since we aren't fully modeled (in order for
|
|
// dynamic update to work correctly)
|
|
RuntimeTransactionHandle handleInstance = this.transactionHandle.Get(context);
|
|
Fx.Assert(handleInstance != null, "RuntimeTransactionHandle is null");
|
|
|
|
//This is used by InternalReceiveMessage to update the InitiatingTransaction so that we can later call Commit/Complete on it
|
|
context.Properties.Add(TransactedReceiveData.TransactedReceiveDataExecutionPropertyName, new TransactedReceiveData());
|
|
|
|
RuntimeTransactionHandle foundHandle = context.Properties.Find(handleInstance.ExecutionPropertyName) as RuntimeTransactionHandle;
|
|
if (foundHandle == null)
|
|
{
|
|
context.Properties.Add(handleInstance.ExecutionPropertyName, handleInstance);
|
|
}
|
|
else
|
|
{
|
|
//nested case
|
|
if (foundHandle.SuppressTransaction)
|
|
{
|
|
throw FxTrace.Exception.AsError(new InvalidOperationException(SR2.CannotNestTransactedReceiveScopeWhenAmbientHandleIsSuppressed(this.DisplayName)));
|
|
}
|
|
|
|
// Verify if TRS is root and if the foundHandle is not from the parent HandleScope<RTH>
|
|
if (foundHandle.GetCurrentTransaction(context) != null)
|
|
{
|
|
handleInstance = foundHandle;
|
|
this.isNested.Set(context, true);
|
|
}
|
|
}
|
|
context.ScheduleActivity(this.Request, new CompletionCallback(OnReceiveCompleted));
|
|
}
|
|
|
|
void OnReceiveCompleted(NativeActivityContext context, ActivityInstance completedInstance)
|
|
{
|
|
if (this.Body != null)
|
|
{
|
|
context.ScheduleActivity(this.Body, new CompletionCallback(OnBodyCompleted));
|
|
}
|
|
else if (completedInstance.State == ActivityInstanceState.Closed)
|
|
{
|
|
OnBodyCompleted(context, completedInstance);
|
|
}
|
|
}
|
|
|
|
void OnBodyCompleted(NativeActivityContext context, ActivityInstance completedInstance)
|
|
{
|
|
TransactedReceiveData transactedReceiveData = context.Properties.Find(TransactedReceiveData.TransactedReceiveDataExecutionPropertyName) as TransactedReceiveData;
|
|
Fx.Assert(transactedReceiveData != null, "TransactedReceiveScope.OnBodyComplete - transactedreceivedata is null");
|
|
|
|
//Non Nested
|
|
if (!this.isNested.Get(context))
|
|
{
|
|
Fx.Assert(transactedReceiveData.InitiatingTransaction != null, "TransactedReceiveScope.OnBodyComplete - Initiating transaction is null");
|
|
System.Transactions.CommittableTransaction committableTransaction = transactedReceiveData.InitiatingTransaction as System.Transactions.CommittableTransaction;
|
|
//If the initiating transaction was a committable transaction => this is a server side only transaction. Commit it here instead of letting the dispatcher deal with it
|
|
//since we are Auto Complete = false and we want the completion of the TransactedReceiveScope to initiate the Commit.
|
|
if (committableTransaction != null)
|
|
{
|
|
committableTransaction.BeginCommit(TransactionCommitAsyncCallback, committableTransaction);
|
|
}
|
|
else
|
|
{
|
|
//If the initiating transaction was a dependent transaction instead => this is a flowed in transaction, let's just complete the dependent clone
|
|
System.Transactions.DependentTransaction dependentTransaction = transactedReceiveData.InitiatingTransaction as System.Transactions.DependentTransaction;
|
|
Fx.Assert(dependentTransaction != null, "TransactedReceiveScope.OnBodyComplete - DependentClone was null");
|
|
dependentTransaction.Complete();
|
|
}
|
|
}
|
|
else //Nested scenario - e.g TRS inside a TSA and in a flow case :- we still need to complete the dependent transaction
|
|
{
|
|
System.Transactions.DependentTransaction dependentTransaction = transactedReceiveData.InitiatingTransaction as System.Transactions.DependentTransaction;
|
|
if (dependentTransaction != null)
|
|
{
|
|
dependentTransaction.Complete();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void TransactionCommitCallback(IAsyncResult result)
|
|
{
|
|
System.Transactions.CommittableTransaction committableTransaction = result.AsyncState as System.Transactions.CommittableTransaction;
|
|
Fx.Assert(committableTransaction != null, "TransactedReceiveScope - In the static TransactionCommitCallback, the committable transaction was null");
|
|
try
|
|
{
|
|
committableTransaction.EndCommit(result);
|
|
}
|
|
catch (System.Transactions.TransactionException ex)
|
|
{
|
|
//At this point, the activity has completed. Since the runtime is enlisted in the transaction, it knows that the transaction aborted.
|
|
//The runtime will do the right thing based on the AbortInstanceOnTransactionFailure flag. We simply trace out that the call to EndCommit failed from this static callback
|
|
if (TD.TransactedReceiveScopeEndCommitFailedIsEnabled())
|
|
{
|
|
TD.TransactedReceiveScopeEndCommitFailed(committableTransaction.TransactionInformation.LocalIdentifier, ex.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
class ObtainType : CodeActivity<Type>
|
|
{
|
|
public ObtainType()
|
|
{
|
|
}
|
|
|
|
public InArgument<Activity> Input
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
protected override void CacheMetadata(CodeActivityMetadata metadata)
|
|
{
|
|
RuntimeArgument inputArgument = new RuntimeArgument("Input", typeof(Activity), ArgumentDirection.In);
|
|
if (this.Input == null)
|
|
{
|
|
this.Input = new InArgument<Activity>();
|
|
}
|
|
metadata.Bind(this.Input, inputArgument);
|
|
|
|
RuntimeArgument resultArgument = new RuntimeArgument("Result", typeof(Type), ArgumentDirection.Out);
|
|
metadata.Bind(this.Result, resultArgument);
|
|
|
|
metadata.SetArgumentsCollection(
|
|
new Collection<RuntimeArgument>
|
|
{
|
|
inputArgument,
|
|
resultArgument
|
|
});
|
|
}
|
|
|
|
protected override Type Execute(CodeActivityContext context)
|
|
{
|
|
return this.Input.Get(context).GetType();
|
|
}
|
|
}
|
|
|
|
class NestedChildTransactionScopeActivityAbortInstanceFlagValidator : CodeActivity<bool>
|
|
{
|
|
public InArgument<Activity> Child
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
protected override void CacheMetadata(CodeActivityMetadata metadata)
|
|
{
|
|
RuntimeArgument childArgument = new RuntimeArgument("Child", typeof(Activity), ArgumentDirection.In);
|
|
if (this.Child == null)
|
|
{
|
|
this.Child = new InArgument<Activity>();
|
|
}
|
|
metadata.Bind(this.Child, childArgument);
|
|
|
|
RuntimeArgument resultArgument = new RuntimeArgument("Result", typeof(bool), ArgumentDirection.Out);
|
|
metadata.Bind(this.Result, resultArgument);
|
|
|
|
metadata.SetArgumentsCollection(
|
|
new Collection<RuntimeArgument>
|
|
{
|
|
childArgument,
|
|
resultArgument
|
|
});
|
|
}
|
|
|
|
protected override bool Execute(CodeActivityContext context)
|
|
{
|
|
Activity child = this.Child.Get(context);
|
|
|
|
if (child != null)
|
|
{
|
|
TransactionScope transactionScopeActivity = child as TransactionScope;
|
|
Fx.Assert(transactionScopeActivity != null, "Child was not of expected type");
|
|
|
|
//We dont care whether the flag was explicitly set
|
|
// a) We cant tell whether the flag was explicitly set on the child
|
|
// b) This is mostly a scenario where the WF calls into a library. It is OK
|
|
// to flag the warning either
|
|
|
|
return transactionScopeActivity.AbortInstanceOnTransactionFailure;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Message = new InArgument<string>(env => SR.AbortInstanceOnTransactionFailureDoesNotMatch(child.Get(env).DisplayName, this.DisplayName)),
|
|
class NestedChildTransactionScopeActivityAbortInstanceFlagValidatorMessage : CodeActivity<string>
|
|
{
|
|
public InArgument<Activity> Child
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public InArgument<string> ParentDisplayName
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
protected override void CacheMetadata(CodeActivityMetadata metadata)
|
|
{
|
|
RuntimeArgument childArgument = new RuntimeArgument("Child", typeof(Activity), ArgumentDirection.In);
|
|
if (this.Child == null)
|
|
{
|
|
this.Child = new InArgument<Activity>();
|
|
}
|
|
metadata.Bind(this.Child, childArgument);
|
|
|
|
RuntimeArgument parentDisplayNameArgument = new RuntimeArgument("ParentDisplayName", typeof(string), ArgumentDirection.In);
|
|
if (this.ParentDisplayName == null)
|
|
{
|
|
this.ParentDisplayName = new InArgument<string>();
|
|
}
|
|
metadata.Bind(this.ParentDisplayName, parentDisplayNameArgument);
|
|
|
|
RuntimeArgument resultArgument = new RuntimeArgument("Result", typeof(string), ArgumentDirection.Out);
|
|
if (this.Result == null)
|
|
{
|
|
this.Result = new OutArgument<string>();
|
|
}
|
|
metadata.Bind(this.Result, resultArgument);
|
|
|
|
metadata.SetArgumentsCollection(
|
|
new Collection<RuntimeArgument>
|
|
{
|
|
childArgument,
|
|
parentDisplayNameArgument,
|
|
resultArgument
|
|
});
|
|
}
|
|
|
|
protected override string Execute(CodeActivityContext context)
|
|
{
|
|
// env => SR.AbortInstanceOnTransactionFailureDoesNotMatch(child.Get(env).DisplayName, this.DisplayName)
|
|
return SR.AbortInstanceOnTransactionFailureDoesNotMatch(this.Child.Get(context).DisplayName, this.ParentDisplayName.Get(context));
|
|
}
|
|
}
|
|
}
|
|
}
|