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.
Create an empty Visual Studio 2010 solution.
Add a new console application called
Service
to the solution.Add references to the following assemblies:
System.Runtime.Serialization
System.ServiceModel
System.ServiceModel.Activities
Add a new class called
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
Add a new class derived from NativeActivity called
AccessIdentityScope
.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; }
Implement the constructor
public AccessIdentityScope() : base() { this.children = new Collection<Activity>(); this.variables = new Collection<Variable>(); this.currentIndex = new Variable<int>(); }
Implement the
Activities
andVariables
properties.public Collection<Activity> Activities { get { return this.children; } } public Collection<Variable> Variables { get { return this.variables; } }
Override CacheMetadata
protected override void CacheMetadata(NativeActivityMetadata metadata) { //call base.CacheMetadata to add the Activities and Variables to this activity's metadata base.CacheMetadata(metadata); //add the private implementation variable: currentIndex metadata.AddImplementationVariable(this.currentIndex); }
Override Execute
protected override void Execute(NativeActivityContext context) { // 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); }
Open the existing
Program
class.Define the following constants:
class Program { const string addr = "https://localhost:8080/Service"; static XName contract = XName.Get("IService", "http://tempuri.org"); }
Add a static method called
GetWorkflowService
that creates the workflow service.static Activity GetServiceWorkflow() { Variable<string> echoString = new Variable<string>(); // 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) } } } } } }; }
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(); } }
Create a new console application project called
Client
.Add references to the following assemblies:
System.Activities
System.ServiceModel
System.ServiceModel.Activities
Open the generated Program.cs file and add a static method called
GetClientWorkflow
to create the client workflow.static Activity GetClientWorkflow() { Variable<string> echoString = new Variable<string>(); 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) ) }, } }; } }
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.
// 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) ) },
}
};
}
}
}
Authoring Workflows, Activities, and Expressions Using Imperative Code