Accessing OperationContext from a Workflow Service
This topic applies to Windows Workflow Foundation 4 (WF4).
To access the OperationContext inside a workflow service, you must implement the IReceiveMessageCallback interface in a custom execution property. Override the OnReceiveMessage method which is passed a reference to the OperationContext. This topic will walk you through implementing this execution property to retrieve a custom header, as well as a custom activity that will surface this property to the Receive at runtime. The custom activity will implement the same behavior as a Sequence activity, except that when a Receive is placed inside of it, the IReceiveMessageCallback will be called and the OperationContext information will be retrieved. This topic also shows how to access the client-side OperationContext to add outgoing headers via the ISendMessageCallback interface.
Implement the Service-side IReceiveMessageCallback
Create an empty Visual Studio 2010 solution.
Add a new console application called
Service
to the solution.Add references to the following assemblies:
System.Runtime.Serialization
System.ServiceModel
System.ServiceModel.Activities
Add a new class called
ReceiveInstanceIdCallback
and implement IReceiveMessageCallback as shown in the following example.class ReceiveInstanceIdCallback : IReceiveMessageCallback { public const string HeaderName = "InstanceIdHeader"; public const string HeaderNS = "http://Microsoft.Samples.AccessingOperationContext"; public void OnReceiveMessage(System.ServiceModel.OperationContext operationContext, System.Activities.ExecutionProperties activityExecutionProperties) { try { Guid instanceId = operationContext.IncomingMessageHeaders.GetHeader<Guid>(HeaderName, HeaderNS); Console.WriteLine("Received a message from a workflow with instanceId = {0}", instanceId); } catch (MessageHeaderException) { Console.WriteLine("This message must not be from a workflow."); } } }
This code uses the OperationContext passed into the method to access the incoming message’s headers.
Implement a Service-side Native activity to add the IReceiveMessageCallback implementation to the NativeActivityContext
Add a new class derived from NativeActivity called
ReceiveInstanceIdScope
.Add local variables to keep track of child activities, variables, current activity index, and a CompletionCallback callback.
public sealed class ReceiveInstanceIdScope : NativeActivity { Collection<Activity> children; Collection<Variable> variables; Variable<int> currentIndex; CompletionCallback onChildComplete; }
Implement the constructor
public ReceiveInstanceIdScope() : base() { this.children = new Collection<Activity>(); this.variables = new Collection<Variable>(); this.currentIndex = new Variable<int>(); } }
Implement the
Activities
andVariables
properties.public Collection<Activity> Activities { get { return this.children; } } public Collection<Variable> Variables { get { return this.variables; } }
Override CacheMetadata
protected override void CacheMetadata(NativeActivityMetadata metadata) { //call base.CacheMetadata to add the Activities and Variables to this activity's metadata base.CacheMetadata(metadata); //add the private implementation variable: currentIndex metadata.AddImplementationVariable(this.currentIndex); }
Override Execute
protected override void Execute( NativeActivityContext context) { context.Properties.Add("ReceiveInstanceIdCallback", new ReceiveInstanceIdCallback()); InternalExecute(context, null); } void InternalExecute(NativeActivityContext context, ActivityInstance instance) { //grab the index of the current Activity int currentActivityIndex = this.currentIndex.Get(context); if (currentActivityIndex == Activities.Count) { //if the currentActivityIndex is equal to the count of MySequence's Activities //MySequence is complete return; } if (this.onChildComplete == null) { //on completion of the current child, have the runtime call back on this method this.onChildComplete = new CompletionCallback(InternalExecute); } //grab the next Activity in MySequence.Activities and schedule it Activity nextChild = Activities[currentActivityIndex]; context.ScheduleActivity(nextChild, this.onChildComplete); //increment the currentIndex this.currentIndex.Set(context, ++currentActivityIndex); }
Implement the workflow service
Open the existing
Program
class.Define the following constants:
class Program { const string addr = "https://localhost:8080/Service"; static XName contract = XName.Get("IService", "http://tempuri.org"); }
Add a static method called
GetWorkflowService
that creates the workflow service.static Activity GetServiceWorkflow() { Variable<string> echoString = new Variable<string>(); Receive echoRequest = new Receive { CanCreateInstance = true, ServiceContractName = contract, OperationName = "Echo", Content = new ReceiveParametersContent() { Parameters = { { "echoString", new OutArgument<string>(echoString) } } } }; return new ReceiveInstanceIdScope { Variables = { echoString }, Activities = { echoRequest, new WriteLine { Text = new InArgument<string>( (e) => "Received: " + echoString.Get(e) ) }, new SendReply { Request = echoRequest, Content = new SendParametersContent() { Parameters = { { "result", new InArgument<string>(echoString) } } } } } }; }
In the existing
Main
method, host the workflow service.static void Main(string[] args) { string addr = "https://localhost:8080/Service"; using (WorkflowServiceHost host = new WorkflowServiceHost(GetServiceWorkflow())) { host.AddServiceEndpoint(contract, new BasicHttpBinding(), addr); host.Open(); Console.WriteLine("Service waiting at: " + addr); Console.WriteLine("Press [ENTER] to exit"); Console.ReadLine(); host.Close(); } }
Implement the Client-side ISendMessageCallback
Add a new console application called
Service
to the solution.Add references to the following assemblies:
System.Runtime.Serialization
System.ServiceModel
System.ServiceModel.Activities
Add a new class called
SendInstanceIdCallback
and implement ISendMessageCallback as shown in the following example.class SendInstanceIdCallback : ISendMessageCallback { public const string HeaderName = "InstanceIdHeader"; public const string HeaderNS = "http://Microsoft.Samples.AccessingOperationContext"; public Guid InstanceId { get; set; } public void OnSendMessage(System.ServiceModel.OperationContext operationContext) { operationContext.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader(HeaderName, HeaderNS, this.InstanceId)); } }
This code uses the OperationContext passed into the method to add a custom header to the incoming message.
Implement a Client-side Native activity to add the client-side ISendMessageCallback implementation to the NativeActivityContext
Add a new class derived from NativeActivity called
SendInstanceIdScope
.Add local variables to keep track of child activities, variables, current activity index, and a CompletionCallback callback.
public sealed class SendInstanceIdScope : NativeActivity { Collection<Activity> children; Collection<Variable> variables; Variable<int> currentIndex; CompletionCallback onChildComplete; }
Implement the constructor
public SendInstanceIdScope() : base() { this.children = new Collection<Activity>(); this.variables = new Collection<Variable>(); this.currentIndex = new Variable<int>(); }
Implement the
Activities
andVariables
properties.public Collection<Activity> Activities { get { return this.children; } } public Collection<Variable> Variables { get { return this.variables; } }
Override CacheMetadata
protected override void CacheMetadata(NativeActivityMetadata metadata) { //call base.CacheMetadata to add the Activities and Variables to this activity's metadata base.CacheMetadata(metadata); //add the private implementation variable: currentIndex metadata.AddImplementationVariable(this.currentIndex); }
Override Execute
protected override void Execute( NativeActivityContext context) { context.Properties.Add("SendInstanceIdCallback", new SendInstanceIdCallback() { InstanceId = context.WorkflowInstanceId }); InternalExecute(context, null); } void InternalExecute(NativeActivityContext context, ActivityInstance instance) { //grab the index of the current Activity int currentActivityIndex = this.currentIndex.Get(context); if (currentActivityIndex == Activities.Count) { //if the currentActivityIndex is equal to the count of MySequence's Activities //MySequence is complete return; } if (this.onChildComplete == null) { //on completion of the current child, have the runtime call back on this method this.onChildComplete = new CompletionCallback(InternalExecute); } //grab the next Activity in MySequence.Activities and schedule it Activity nextChild = Activities[currentActivityIndex]; context.ScheduleActivity(nextChild, this.onChildComplete); //increment the currentIndex this.currentIndex.Set(context, ++currentActivityIndex); } protected override void Execute( NativeActivityContext context) { context.Properties.Add("ReceiveInstanceIdCallback", new ReceiveInstanceIdCallback()); InternalExecute(context, null); } void InternalExecute(NativeActivityContext context, ActivityInstance instance) { //grab the index of the current Activity int currentActivityIndex = this.currentIndex.Get(context); if (currentActivityIndex == Activities.Count) { //if the currentActivityIndex is equal to the count of MySequence's Activities //MySequence is complete return; } if (this.onChildComplete == null) { //on completion of the current child, have the runtime call back on this method this.onChildComplete = new CompletionCallback(InternalExecute); } //grab the next Activity in MySequence.Activities and schedule it Activity nextChild = Activities[currentActivityIndex]; context.ScheduleActivity(nextChild, this.onChildComplete); //increment the currentIndex this.currentIndex.Set(context, ++currentActivityIndex); }
Implement a workflow client
Create a new console application project called
Client
.Add references to the following assemblies:
System.Activities
System.ServiceModel
System.ServiceModel.Activities
Open the generated Program.cs file and add a static method called
GetClientWorkflow
to create the client workflow.static Activity GetClientWorkflow() { Variable<string> echoString = new Variable<string>(); // Define the endpoint Endpoint clientEndpoint = new Endpoint { Binding = new BasicHttpBinding(), AddressUri = new Uri("https://localhost:8080/Service") }; // Configure the Send activity used to send a message Send echoRequest = new Send { Endpoint = clientEndpoint, ServiceContractName = XName.Get("IService", "http://tempuri.org"), OperationName = "Echo", Content = new SendParametersContent() { Parameters = { { "echoString", new InArgument<string>("Hello, World") } } } }; // Place the Send activity in a SendInstanceIdScope. This hooks up the ISendMessageCallback // implementation to the client workflow. return new SendInstanceIdScope { Variables = { echoString }, Activities = { new CorrelationScope { Body = new Sequence { Activities = { // Send the request message echoRequest, // Receive the reply from the service new ReceiveReply { Request = echoRequest, Content = new ReceiveParametersContent { Parameters = { { "result", new OutArgument<string>(echoString) } } } } } } }, new WriteLine { Text = new InArgument<string>( (e) => "Received Text: " + echoString.Get(e) ) }, } }; }
Add the following hosting code to the
Main()
method.static void Main(string[] args) { Activity workflow = GetClientWorkflow(); WorkflowInvoker.Invoke(workflow); WorkflowInvoker.Invoke(workflow); Console.WriteLine("Press [ENTER] to exit"); Console.ReadLine(); }
Example
Here is a complete listing of the source code used in this topic.
// ReceiveInstanceIdScope.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System.Activities;
using System.Collections.ObjectModel;
namespace Microsoft.Samples.AccessingOperationContext.Service
{
public sealed class ReceiveInstanceIdScope : NativeActivity
{
Collection<Activity> children;
Collection<Variable> variables;
Variable<int> currentIndex;
CompletionCallback onChildComplete;
public ReceiveInstanceIdScope()
: base()
{
this.children = new Collection<Activity>();
this.variables = new Collection<Variable>();
this.currentIndex = new Variable<int>();
}
public Collection<Activity> Activities
{
get
{
return this.children;
}
}
public Collection<Variable> Variables
{
get
{
return this.variables;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
//call base.CacheMetadata to add the Activities and Variables to this activity's metadata
base.CacheMetadata(metadata);
//add the private implementation variable: currentIndex
metadata.AddImplementationVariable(this.currentIndex);
}
protected override void Execute(
NativeActivityContext context)
{
context.Properties.Add("ReceiveInstanceIdCallback", new ReceiveInstanceIdCallback());
InternalExecute(context, null);
}
void InternalExecute(NativeActivityContext context, ActivityInstance instance)
{
//grab the index of the current Activity
int currentActivityIndex = this.currentIndex.Get(context);
if (currentActivityIndex == Activities.Count)
{
//if the currentActivityIndex is equal to the count of MySequence's Activities
//MySequence is complete
return;
}
if (this.onChildComplete == null)
{
//on completion of the current child, have the runtime call back on this method
this.onChildComplete = new CompletionCallback(InternalExecute);
}
//grab the next Activity in MySequence.Activities and schedule it
Activity nextChild = Activities[currentActivityIndex];
context.ScheduleActivity(nextChild, this.onChildComplete);
//increment the currentIndex
this.currentIndex.Set(context, ++currentActivityIndex);
}
}
}
// ReceiveInstanceIdScope.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System;
using System.ServiceModel;
using System.ServiceModel.Activities;
namespace Microsoft.Samples.AccessingOperationContext.Service
{
class ReceiveInstanceIdCallback : IReceiveMessageCallback
{
public const string HeaderName = "InstanceIdHeader";
public const string HeaderNS = "http://Microsoft.Samples.AccessingOperationContext";
public void OnReceiveMessage(System.ServiceModel.OperationContext operationContext, System.Activities.ExecutionProperties activityExecutionProperties)
{
try
{
Guid instanceId = operationContext.IncomingMessageHeaders.GetHeader<Guid>(HeaderName, HeaderNS);
Console.WriteLine("Received a message from a workflow with instanceId = {0}", instanceId);
}
catch (MessageHeaderException)
{
Console.WriteLine("This message must not be from a workflow.");
}
}
}
}
// Service.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System;
using System.Activities;
using System.Activities.Statements;
using System.ServiceModel;
using System.ServiceModel.Activities;
using System.Xml.Linq;
namespace Microsoft.Samples.AccessingOperationContext.Service
{
class Program
{
const string addr = "https://localhost:8080/Service";
static XName contract = XName.Get("IService", "http://tempuri.org");
static void Main(string[] args)
{
string addr = "https://localhost:8080/Service";
using (WorkflowServiceHost host = new WorkflowServiceHost(GetServiceWorkflow()))
{
host.AddServiceEndpoint(contract, new BasicHttpBinding(), addr);
host.Open();
Console.WriteLine("Service waiting at: " + addr);
Console.WriteLine("Press [ENTER] to exit");
Console.ReadLine();
host.Close();
}
}
static Activity GetServiceWorkflow()
{
Variable<string> echoString = new Variable<string>();
Receive echoRequest = new Receive
{
CanCreateInstance = true,
ServiceContractName = contract,
OperationName = "Echo",
Content = new ReceiveParametersContent()
{
Parameters = { { "echoString", new OutArgument<string>(echoString) } }
}
};
return new ReceiveInstanceIdScope
{
Variables = { echoString },
Activities =
{
echoRequest,
new WriteLine { Text = new InArgument<string>( (e) => "Received: " + echoString.Get(e) ) },
new SendReply
{
Request = echoRequest,
Content = new SendParametersContent()
{
Parameters = { { "result", new InArgument<string>(echoString) } }
}
}
}
};
}
}
}
// SendInstanceIdCallback.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System;
using System.ServiceModel.Activities;
using System.ServiceModel.Channels;
namespace Microsoft.Samples.AccessingOperationContext.Client
{
class SendInstanceIdCallback : ISendMessageCallback
{
public const string HeaderName = "InstanceIdHeader";
public const string HeaderNS = "http://Microsoft.Samples.AccessingOperationContext";
public Guid InstanceId { get; set; }
public void OnSendMessage(System.ServiceModel.OperationContext operationContext)
{
operationContext.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader(HeaderName, HeaderNS, this.InstanceId));
}
}
}
// SendInstanceIdScope.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System.Activities;
using System.Collections.ObjectModel;
namespace Microsoft.Samples.AccessingOperationContext.Client
{
public sealed class SendInstanceIdScope : NativeActivity
{
Collection<Activity> children;
Collection<Variable> variables;
Variable<int> currentIndex;
CompletionCallback onChildComplete;
public SendInstanceIdScope()
: base()
{
this.children = new Collection<Activity>();
this.variables = new Collection<Variable>();
this.currentIndex = new Variable<int>();
}
public Collection<Activity> Activities
{
get
{
return this.children;
}
}
public Collection<Variable> Variables
{
get
{
return this.variables;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
//call base.CacheMetadata to add the Activities and Variables to this activity's metadata
base.CacheMetadata(metadata);
//add the private implementation variable: currentIndex
metadata.AddImplementationVariable(this.currentIndex);
}
protected override void Execute(
NativeActivityContext context)
{
context.Properties.Add("SendInstanceIdCallback", new SendInstanceIdCallback() { InstanceId = context.WorkflowInstanceId });
InternalExecute(context, null);
}
void InternalExecute(NativeActivityContext context, ActivityInstance instance)
{
//grab the index of the current Activity
int currentActivityIndex = this.currentIndex.Get(context);
if (currentActivityIndex == Activities.Count)
{
//if the currentActivityIndex is equal to the count of MySequence's Activities
//MySequence is complete
return;
}
if (this.onChildComplete == null)
{
//on completion of the current child, have the runtime call back on this method
this.onChildComplete = new CompletionCallback(InternalExecute);
}
//grab the next Activity in MySequence.Activities and schedule it
Activity nextChild = Activities[currentActivityIndex];
context.ScheduleActivity(nextChild, this.onChildComplete);
//increment the currentIndex
this.currentIndex.Set(context, ++currentActivityIndex);
}
}
}
// Client.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System;
using System.Activities;
using System.Activities.Statements;
using System.ServiceModel;
using System.ServiceModel.Activities;
using System.Xml.Linq;
namespace Microsoft.Samples.AccessingOperationContext.Client
{
class Program
{
static void Main(string[] args)
{
Activity workflow = GetClientWorkflow();
WorkflowInvoker.Invoke(workflow);
WorkflowInvoker.Invoke(workflow);
Console.WriteLine("Press [ENTER] to exit");
Console.ReadLine();
}
static Activity GetClientWorkflow()
{
Variable<string> echoString = new Variable<string>();
Endpoint clientEndpoint = new Endpoint
{
Binding = new BasicHttpBinding(),
AddressUri = new Uri("https://localhost:8080/Service")
};
Send echoRequest = new Send
{
Endpoint = clientEndpoint,
ServiceContractName = XName.Get("IService", "http://tempuri.org"),
OperationName = "Echo",
Content = new SendParametersContent()
{
Parameters = { { "echoString", new InArgument<string>("Hello, World") } }
}
};
return new SendInstanceIdScope
{
Variables = { echoString },
Activities =
{
new CorrelationScope
{
Body = new Sequence
{
Activities =
{
echoRequest,
new ReceiveReply
{
Request = echoRequest,
Content = new ReceiveParametersContent
{
Parameters = { { "result", new OutArgument<string>(echoString) } }
}
}
}
}
},
new WriteLine { Text = new InArgument<string>( (e) => "Received Text: " + echoString.Get(e) ) },
}
};
}
}
}
Optional comments.
See Also
Tasks
Concepts
Authoring Workflows, Activities, and Expressions Using Imperative Code