Dela via


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

  1. Create an empty Visual Studio 2010 solution.

  2. Add a new console application called Service to the solution.

  3. Add references to the following assemblies:

    1. System.Runtime.Serialization

    2. System.ServiceModel

    3. System.ServiceModel.Activities

  4. 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

  1. Add a new class derived from NativeActivity called ReceiveInstanceIdScope.

  2. 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;
    }
    
  3. Implement the constructor

    public ReceiveInstanceIdScope()
                : base()
            {
                this.children = new Collection<Activity>();
                this.variables = new Collection<Variable>();
                this.currentIndex = new Variable<int>();
            }
    }
    
  4. Implement the Activities and Variables properties.

    public Collection<Activity> Activities
    {
         get { return this.children; }
    }
    
    public Collection<Variable> Variables
    {
        get { return this.variables; }
    }
    
  5. 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);
    }   
    
  6. 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

  1. Open the existing Program class.

  2. Define the following constants:

    class Program
    {
       const string addr = "https://localhost:8080/Service";
       static XName contract = XName.Get("IService", "http://tempuri.org");
    }
    
  3. 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) } } 
                            }
                        }
                    }
                };
            }
    
  4. 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

  1. Add a new console application called Service to the solution.

  2. Add references to the following assemblies:

    1. System.Runtime.Serialization

    2. System.ServiceModel

    3. System.ServiceModel.Activities

  3. 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

  1. Add a new class derived from NativeActivity called SendInstanceIdScope.

  2. 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;
    }
    
  3. Implement the constructor

    public SendInstanceIdScope()
                : base()
            {
                this.children = new Collection<Activity>();
                this.variables = new Collection<Variable>();
                this.currentIndex = new Variable<int>();
            }
    
  4. Implement the Activities and Variables properties.

    public Collection<Activity> Activities
    {
         get { return this.children; }
    }
    
    public Collection<Variable> Variables
    {
        get { return this.variables; }
    }
    
  5. 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);
    }   
    
  6. 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

  1. Create a new console application project called Client.

  2. Add references to the following assemblies:

    1. System.Activities

    2. System.ServiceModel

    3. System.ServiceModel.Activities

  3. 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) ) },                    
                    }
                };
            }
    
  4. 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

Accessing OperationContext

Concepts

Authoring Workflows, Activities, and Expressions Using Imperative Code

Other Resources

Workflow Services
Workflow Services Samples (WF)