Flowing Transactions into and out of Workflow Services
Workflow services and clients can participate in transactions. For a service operation to become part of an ambient transaction, place a Receive activity within a TransactedReceiveScope activity. Any calls made by a Send or a SendReply activity within the TransactedReceiveScope will also be made within the ambient transaction. A workflow client application can create an ambient transaction by using the TransactionScope activity and call service operations using the ambient transaction. This topic walks you through creating a workflow service and workflow client that participate in transactions.
Warning
If a workflow service instance is loaded within a transaction and the workflow contains a Persist activity, the workflow instance will block until the transaction times out.
Important
Whenever you use a TransactedReceiveScope it is recommended to place all Receives in the workflow within TransactedReceiveScope activities.
Important
When using TransactedReceiveScope and messages arrive in the incorrect order, the workflow will be aborted when trying to deliver the first out of order message. You must make sure your workflow is always at a consistent stopping point when the workflow idles. This will allow you to restart the workflow from a previous persistence point should the workflow be aborted.
Create a shared library
Create a new empty Visual Studio Solution.
Add a new class library project called
Common
. Add references to the following assemblies:System.Activities.dll
System.ServiceModel.dll
System.ServiceModel.Activities.dll
System.Transactions.dll
Add a new class called
PrintTransactionInfo
to theCommon
project. This class is derived from NativeActivity and overloads the Execute method.using System; using System; using System.Activities; using System.Transactions; namespace Common { public class PrintTransactionInfo : NativeActivity { protected override void Execute(NativeActivityContext context) { RuntimeTransactionHandle rth = context.Properties.Find(typeof(RuntimeTransactionHandle).FullName) as RuntimeTransactionHandle; if (rth == null) { Console.WriteLine("There is no ambient RuntimeTransactionHandle"); } Transaction t = rth.GetCurrentTransaction(context); if (t == null) { Console.WriteLine("There is no ambient transaction"); } else { Console.WriteLine("Transaction: {0} is {1}", t.TransactionInformation.DistributedIdentifier, t.TransactionInformation.Status); } } } }
This is a native activity that displays information about the ambient transaction and is used in both the service and client workflows used in this topic. Build the solution to make this activity available in the Common section of the Toolbox.
Implement the workflow service
Add a new WCF Workflow Service, called
WorkflowService
to theCommon
project. To do this right click theCommon
project, select Add, New Item ..., Select Workflow under Installed Templates and select WCF Workflow Service.Delete the default
ReceiveRequest
andSendResponse
activities.Drag and drop a WriteLine activity into the
Sequential Service
activity. Set the text property to"Workflow Service starting ..."
as shown in the following example.![Adding a WriteLine activity to the Sequential Service activity(./media/flowing-transactions-into-and-out-of-workflow-services/add-writeline-sequential-service.jpg)
Drag and drop a TransactedReceiveScope after the WriteLine activity. The TransactedReceiveScope activity can be found in the Messaging section of the Toolbox. The TransactedReceiveScope activity is composed of two sections Request and Body. The Request section contains the Receive activity. The Body section contains the activities to execute within a transaction after a message has been received.
Select the TransactedReceiveScope activity and click the Variables button. Add the following variables.
Note
You can delete the data variable that is there by default. You can also use the existing handle variable.
Drag and drop a Receive activity within the Request section of the TransactedReceiveScope activity. Set the following properties:
Property Value CanCreateInstance True (check the checkbox) OperationName StartSample ServiceContractName ITransactionSample The workflow should look like this:
Click the Define... link in the Receive activity and make the following settings:
Drag and drop a Sequence activity into the Body section of the TransactedReceiveScope. Within the Sequence activity drag and drop two WriteLine activities and set the Text properties as shown in the following table.
Activity Value 1st WriteLine "Service: Receive Completed" 2nd WriteLine "Service: Received = " + requestMessage The workflow should now look like this:
Drag and drop the
PrintTransactionInfo
activity after the second WriteLine activity in the Body in the TransactedReceiveScope activity.Drag and drop an Assign activity after the
PrintTransactionInfo
activity and set its properties according to the following table.Property Value To replyMessage Value "Service: Sending reply." Drag and drop a WriteLine activity after the Assign activity and set its Text property to "Service: Begin reply."
The workflow should now look like this:
Right click the Receive activity and select Create SendReply and paste it after the last WriteLine activity. Click the Define... link in the
SendReplyToReceive
activity and make the following settings.Drag and drop a WriteLine activity after the
SendReplyToReceive
activity and set it’s Text property to "Service: Reply sent."Drag and drop a WriteLine activity at the bottom of the workflow and set its Text property to "Service: Workflow ends, press ENTER to exit."
The completed service workflow should look like this:
Implement the workflow client
Add a new WCF Workflow application, called
WorkflowClient
to theCommon
project. To do this right click theCommon
project, select Add, New Item ..., Select Workflow under Installed Templates and select Activity.Drag and drop a Sequence activity onto the design surface.
Within the Sequence activity drag and drop a WriteLine activity and set its Text property to
"Client: Workflow starting"
. The workflow should now look like this:Drag and drop a TransactionScope activity after the WriteLine activity. Select the TransactionScope activity, click the Variables button and add the following variables.
Drag and drop a Sequence activity into the body of the TransactionScope activity.
Drag and drop a
PrintTransactionInfo
activity within the SequenceDrag and drop a WriteLine activity after the
PrintTransactionInfo
activity and set its Text property to "Client: Beginning Send". The workflow should now look like this:Drag and drop a Send activity after the Assign activity and set the following properties:
Property Value EndpointConfigurationName workflowServiceEndpoint OperationName StartSample ServiceContractName ITransactionSample The workflow should now look like this:
Click the Define... link and make the following settings:
Right click the Send activity and select Create ReceiveReply. The ReceiveReply activity will be automatically placed after the Send activity.
Click the Define... link on the ReceiveReplyForSend activity and make the following settings:
Drag and drop a WriteLine activity between the Send and ReceiveReply activities and set its Text property to "Client: Send complete."
Drag and drop a WriteLine activity after the ReceiveReply activity and set its Text property to "Client side: Reply received = " + replyMessage
Drag and drop a
PrintTransactionInfo
activity after the WriteLine activity.Drag and drop a WriteLine activity at the end of the workflow and set its Text property to "Client workflow ends." The completed client workflow should look like the following diagram.
Build the solution.
Create the Service application
Add a new Console Application project called
Service
to the solution. Add references to the following assemblies:System.Activities.dll
System.ServiceModel.dll
System.ServiceModel.Activities.dll
Open the generated Program.cs file and the following code:
static void Main() { Console.WriteLine("Building the server."); using (WorkflowServiceHost host = new WorkflowServiceHost(new DeclarativeServiceWorkflow(), new Uri("net.tcp://localhost:8000/TransactedReceiveService/Declarative"))) { //Start the server host.Open(); Console.WriteLine("Service started."); Console.WriteLine(); Console.ReadLine(); //Shutdown host.Close(); }; }
Add the following app.config file to the project.
<?xml version="1.0" encoding="utf-8" ?> <!-- Copyright © Microsoft Corporation. All rights reserved. --> <configuration> <system.serviceModel> <bindings> <netTcpBinding> <binding transactionFlow="true" /> </netTcpBinding> </bindings> </system.serviceModel> </configuration>
Create the client application
Add a new Console Application project called
Client
to the solution. Add a reference to System.Activities.dll.Open the program.cs file and add the following code.
class Program { private static AutoResetEvent syncEvent = new AutoResetEvent(false); static void Main(string[] args) { //Build client Console.WriteLine("Building the client."); WorkflowApplication client = new WorkflowApplication(new DeclarativeClientWorkflow()); client.Completed = Program.Completed; client.Aborted = Program.Aborted; client.OnUnhandledException = Program.OnUnhandledException; //Wait for service to start Console.WriteLine("Press ENTER once service is started."); Console.ReadLine(); //Start the client Console.WriteLine("Starting the client."); client.Run(); syncEvent.WaitOne(); //Sample complete Console.WriteLine(); Console.WriteLine("Client complete. Press ENTER to exit."); Console.ReadLine(); } private static void Completed(WorkflowApplicationCompletedEventArgs e) { Program.syncEvent.Set(); } private static void Aborted(WorkflowApplicationAbortedEventArgs e) { Console.WriteLine("Client Aborted: {0}", e.Reason); Program.syncEvent.Set(); } private static UnhandledExceptionAction OnUnhandledException(WorkflowApplicationUnhandledExceptionEventArgs e) { Console.WriteLine("Client had an unhandled exception: {0}", e.UnhandledException); return UnhandledExceptionAction.Cancel; } }