Accessing Identity Information inside a Workflow Service

This topic applies to Windows Workflow Foundation 4 (WF4).

To access identity information inside a workflow service, you must implement the IReceiveMessageCallback interface in a custom execution property. In the OnReceiveMessage method you can access the ServiceSecurityContext to access identity information. This topic will walk you through implementing this execution property, as well as a custom activity that will surface this property to the Receive activity 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 identity information will be retrieved.

Implement 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 AccessIdentityCallback and implement IReceiveMessageCallback as shown in the following example.

    C#
    class AccessIdentityCallback : IReceiveMessageCallback
    {
       public void OnReceiveMessage(System.ServiceModel.OperationContext operationContext, System.Activities.ExecutionProperties activityExecutionProperties)
       {
          try
          {
             Console.WriteLine("Received a message from a workflow with the following identity");
             Console.WriteLine("Windows Identity Name: {0}", operationContext.ServiceSecurityContext.WindowsIdentity.Name);
             Console.WriteLine("Windows Identity User: {0}", operationContext.ServiceSecurityContext.WindowsIdentity.User);
             Console.WriteLine("Windows Identity IsAuthenticated: {0}", operationContext.ServiceSecurityContext.WindowsIdentity.IsAuthenticated);
          }          
          catch (Exception ex)
          {
             Console.WriteLine("An exception occurred: " + ex.Message);
          }
        }
    }
    

    This code uses the OperationContext passed into the method to access identity information.

Implement a Native activity to add the IReceiveMessageCallback implementation to the NativeActivityContext

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

  2. Add local variables to keep track of child activities, variables, current activity index, and a CompletionCallback callback.

    public sealed class AccessIdentityScope : NativeActivity
    {
        Collection<Activity> children;
        Collection<Variable> variables;
        Variable<int> currentIndex;
        CompletionCallback onChildComplete;
    }
    
  3. Implement the constructor

    public AccessIdentityScope() : 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)
    {
       // Add the IReceiveMessageCallback implementation as an Execution property 
       context.Properties.Add("AccessIdentityCallback", new AccessIdentityCallback());
       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>();
    
       // Create the Receive activity
       Receive echoRequest = new Receive
       {
          CanCreateInstance = true,
          ServiceContractName = contract,
          OperationName = "Echo",
          Content = new ReceiveParametersContent()
          {
             Parameters = { { "echoString", new OutArgument<string>(echoString) } }
          }
       };
    
       return new AccessIdentityScope
       {
          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()))
       {
          WSHttpBinding binding = new WSHttpBinding(SecurityMode.Message);
          host.AddServiceEndpoint(contract, binding, addr);
    
          host.Open();
          Console.WriteLine("Service waiting at: " + addr);
          Console.WriteLine("Press [ENTER] to exit");
          Console.ReadLine();
          host.Close();
       }
    }
    

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>();
    
       Endpoint clientEndpoint = new Endpoint
       {
          Binding = new WSHttpBinding(SecurityMode.Message),
          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 Sequence
       {
          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) ) },                    
             }
          };
       }
    }
    
  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.

    // AccessIdentityCallback.cs
    //----------------------------------------------------------------
    // Copyright (c) Microsoft Corporation.  All rights reserved.
    //----------------------------------------------------------------
    
    using System;
    using System.ServiceModel;
    using System.ServiceModel.Activities;
    
    namespace Microsoft.Samples.AccessingOperationContext.Service
    {
        class AccessIdentityCallback : 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 the following identity" ); // with instanceId = {0}", instanceId);
                    Console.WriteLine("Windows Identity Name: {0}", operationContext.ServiceSecurityContext.WindowsIdentity.Name);
                    Console.WriteLine("Windows Identity User: {0}", operationContext.ServiceSecurityContext.WindowsIdentity.User);
                    Console.WriteLine("Windows Identity IsAuthenticated: {0}", operationContext.ServiceSecurityContext.WindowsIdentity.IsAuthenticated);
                }
                catch (MessageHeaderException)
                {
                    Console.WriteLine("This message must not be from a workflow.");
                }
                catch (Exception ex)
                {
                    Console.WriteLine("An exception occurred: " + ex.Message);
                }
            }
        }
    }
    // AccessIdentityScope.cs
    //----------------------------------------------------------------
    // Copyright (c) Microsoft Corporation.  All rights reserved.
    //----------------------------------------------------------------
    
    using System.Activities;
    using System.Collections.ObjectModel;
    
    namespace Microsoft.Samples.AccessingOperationContext.Service
    {
        public sealed class AccessIdentityScope : NativeActivity
        {
            Collection<Activity> children;
            Collection<Variable> variables;
            Variable<int> currentIndex;
            CompletionCallback onChildComplete;
    
            public AccessIdentityScope()
                : 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("AccessIdentityCallback", new AccessIdentityCallback());
                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);
            }
        }
    }
    // 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()))
                {
                    WSHttpBinding binding = new WSHttpBinding(SecurityMode.Message);
                    host.AddServiceEndpoint(contract, binding, 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 AccessIdentityScope
                {
                    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) } } 
                            }
                        }
                    }
                };
            }
        }
    }
    // 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 WSHttpBinding(SecurityMode.Message),
                    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 Sequence
                {
                    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) ) },                    
                    }
                };
            }
        }
    }

See Also

Tasks

Accessing OperationContext

Concepts

Authoring Workflows, Activities, and Expressions Using Imperative Code

Other Resources

Workflow Services
Workflow Services Samples (WF)