There are quite a a few cases where it is useful to have a duplex communications. An obvious candidate is allowing a service to notify the user interface application of the progress so the user knows what is happening. Normally, when using plain WCF, you would use a ServiceContract attribute with a CallbackContract that specified the service uses duplex communications. For example something like the following code on the server:
[ServiceContract(CallbackContract = typeof(IService1Callback))]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
[ServiceContract]
public interface IService1Callback
{
[OperationContract]
void Reply(string message);
}
along with a client like this:
class Program
{
static void Main(string[] args)
{
var callback = new Service1Callback();
var proxy = new Service1Client(new InstanceContext(callback));
Console.WriteLine(proxy.GetData(42));
Console.ReadLine();
}
}
class Service1Callback : IService1Callback
{
public void Reply(string message)
{
Console.WriteLine(message);
}
}
But with a workflow service this doesn’t work because, without the ServiceContract attribute, there is no way to specify the CallbackContract. Yet workflows support duplex communications. So if we can’t use a CallbackContract how can we do so?
Workflow Services and Durable Duplex
Instead of the normal WCF duplex services workflow services use a mechanism that is called durable duplex. This is a more disconnected way of doing duplex communications that has a big advantage in that both communications work independently of each other. And that means the usual drawbacks of duplex communications don’t apply here. We can even use a completely different binding on the callback than on the original request. There is however a downside as the client has to create its own ServiceHost and act as a complete WCF service, a little more involved than a normal duplex request. Also settings a workflow, and the client, up for durable duplex takes a bit more configuring. So lets take a look how to do so.
Creating the Workflow Service
First step is to create the workflow service. This starts just like any other WCF Workflow Service Application. The durable duplex mechanism requires a context binding so the first change to the project is to use the wsHttpContextBinding. An easy change using the WCF protocolMapping:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<protocolMapping>
<add scheme="http" binding="wsHttpContextBinding" />
</protocolMapping>
<!-- Remainder deleted -->
</system.serviceModel>
</configuration>
Next step is adding a client console application and making sure we can call the workflow service. Add a console application, add a service reference to the workflow service and add the following code to the Main() function:
static void Main(string[] args)
{
var proxy = new ServiceClient();
Console.WriteLine(proxy.GetData(42));
Console.ReadLine();
}
No big deal so far. Now lets add the duplex part.
First of all we need to add a ServiceHost to the client exposing a callback contract and a callback service definition for the workflow service to call back to. Unlike a normal duplex service, where you define the contract on the service, we need to define this contract on the client using the following definition:
[ServiceContract]
interface IServiceCallback
{
[OperationContract(IsOneWay = true)]
void Update(string message);
}
[ServiceBehavior(InstanceContextMode= InstanceContextMode.Single)]
class ServiceCallback : IServiceCallback
{
public void Update(string message)
{
Console.WriteLine(message);
}
}
Next step is to update the Main() function to host this service:
static void Main(string[] args)
{
var address = "http://localhost:8080/ServiceCallback";
var serviceCallback = new ServiceCallback();
var host = new ServiceHost(serviceCallback, new Uri(address));
host.Open();
var proxy = new ServiceClient();
Console.WriteLine(proxy.GetData(42));
Console.ReadLine();
proxy.Close();
host.Close();
}
Note that this looks just like a service would when self hosting.
Next step is to get the workflow service to call back to the client. For this we need to do a could of things. First of all we need to let the workflow service know where to call back to. This is done by adding a CallbackContextMessageProperty with the callback address to the request header.
This can be done with an OperationContextScope by replacing the proxy.GetDate() call with the following code in the Main() function:
var proxy = new ServiceClient();
using (new OperationContextScope((IContextChannel)proxy.InnerChannel))
{
var context = new CallbackContextMessageProperty(address, new Dictionary<string, string>());
OperationContext.Current.OutgoingMessageProperties.Add(CallbackContextMessageProperty.Name, context);
Console.WriteLine(proxy.GetData(42));
}
Next we need to update the workflow itself.
The first thing we need to add is a CorrelationHandle variable to hold the callback correlation.
Next we need to initialize the callbackHandle using the CorrelationInitializer on the GetData Receive activity.
Next we need a Send activity to send the response to the client setting its CorrelatesWith to the same callbackHandle just initialized.
Finally we set the Send activity properties to match the client callback service as follows:
Note that we leave both the AddressUri and EndpointAddress empty, there is no config either, because this is provide through the request context and the callback correlation handle. We are also setting and set the binding to basicHttpBinding because that was what the client was using.
And finally we need to add a message to pass back like this:
So with all this complete we can run our client and service and have the service call back into the client.
A few things that might trip you up.
Forgetting to set the callback CorrelationHandle results in no messages being sent to the client. In fact an InvalidOperationException occurs with message "Endpoint with Name='<not specified>' and ServiceContract '<not specified>' has a null or empty Uri property. A Uri for this Endpoint must be provided.". This is caused by the Send activity not being able to find its callback address.
Not passing an empty context from the client. The CallbackContextMessageProperty has a few constructor overloads, one just taking a single EndpointAddress and no context parameter. Seems like a good choice as we don’t need to specify any context. Unfortunately this results in an ArgumentNullException with message "Value cannot be null. Parameter name: context". Again when the Send activity tries to find its callback address
How about Silverlight?
And the is of course the biggest problem of them all: what if you can’t create a ServiceHost on the client? In that case you are done for and there is no way to use this durable duplex mechanism. And I guess that rules Silverlight out as a possible client [:(]
Conclusion
All in all this works but it is kind of hard to setup the first time because of all the intricacies with the CallbackContextMessageProperty and the callback correlation handle.
Enjoy
www.TheProblemSolver.nl
Wiki.WindowsWorkflowFoundation.eu