Export (0) Print
Expand All

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.

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

Community Additions

ADD
Show:
© 2014 Microsoft