Export (0) Print
Expand All

Accessing OperationContext from a Workflow Service

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 2012 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 = "http://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 = "http://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("http://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();
    }
    
    

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 = "http://localhost:8080/Service";
        static XName contract = XName.Get("IService", "http://tempuri.org");

        static void Main(string[] args)
        {
            string addr = "http://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("http://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.

Show:
© 2014 Microsoft