In our previous
WCF Tutorial, we described how to make a very simple client/server application where clients would call functions on the server through a shared interface. In this tutorial, I'm going to expand that application to allow the server to execute functions on the client by using callbacks. Essentially, this is WCF's mechanism to allow events to be raised from the server to it's connected clients.
If you're new to WCF or you haven't read the previous
WCF Tutorial, I would highly recommend it. This post will be utilizing the example applications produced from the first one, so some explanation may be left out.
All right, let's get into it. Just like with a Service Contract, we need to define an interface that describes available functions that the server can use for callbacks.
using System;
using System.ServiceModel;
public interface ICallbacks
{
[OperationContract(IsOneWay = true)]
void MyCallbackFunction(string callbackValue);
}
Here we've defined an interface with a single function, MyCallbackFunction. We add the OperationContract attribute to the function and mark it as a one-way operation. What we just did is tell the server that it can now execute this function on connected clients.
Unlike a Service Contract, it's now up to the client to implement the interface to give it some functionality. In essence, we're simply doing the exact opposite that was done in the previous tutorial.
public class Callbacks : ICallbacks
{
public void MyCallbackFunction(string callbackValue)
{
Console.WriteLine("Callback Received: {0}", callbackValue);
}
}
That's it for the callback interface. Now we need to make changes to the Service Contract to support it. Let's start with the code created in the previous tutorial.
using System;
using System.ServiceModel;
[ServiceContract]
public interface IStringReverser
{
[OperationContract]
string ReverseString(string value);
}
The first thing we need to do is modify the ServiceContract attribute slightly to indicate the existence of a callback contract.
[[ServiceContract(SessionMode=SessionMode.Required,
CallbackContract=typeof(ICallbacks))]
public interface IStringReverser
{
[OperationContract]
string ReverseString(string value);
}
We added two things to the Service Contract. First we need to make sure WCF maintains session information for the duration of a client's connection. This is required for callback contracts. Secondly, we specify the which interface will be handling the callback contract.
That's all the interface changes we need to make. Next we need to modify the server's implementation of the Service Contract in order to get an instance of the callback contract. Let's start with the code created in the previous tutorial.
public class StringReverser : IStringReverser
{
public string ReverseString(string value)
{
char[] retVal = value.ToCharArray();
int idx = 0;
for (int i = value.Length - 1; i >= 0; i--)
retVal[idx++] = value[i];
return new string(retVal);
}
}
Normally you'd probably use callbacks a little differently, but I'm going to raise the callback whenever the client calls ReverseString. I'm also going to pass the result of ReverseString through the callback.
public class StringReverser : IStringReverser
{
public string ReverseString(string value)
{
char[] retVal = value.ToCharArray();
int idx = 0;
for (int i = value.Length - 1; i >= 0; i--)
retVal[idx++] = value[i];
ICallbacks callbacks =
OperationContext.Current.GetCallbackChannel<ICallbacks>();
callbacks.MyCallbackFunction(new string(retVal));
return new string(retVal);
}
}
The first thing I do is request the current callback channel to get an instance of my callback interface. Now, whenever the server calls functions in the callback interface, they will be executed on the client.
The recommended way to handle callbacks is using a subscription model. Instead of getting the callback channel in a method like this, the service contract should expose a function called Subscribe that can be called by a client. When called, the callback interface should be created like above, then added to a collection for use later. I implemented it this way to keep the examples as clean as possible.
We're almost there. Unfortunately, not every binding will support duplex communication. In our previous tutorial we created a ServiceHost with two bindings: HTTP and Named Pipe. The basic http binding does not support callbacks, however named pipe will, so for this tutorial I have removed the http bindings.
The server's initialization code does not change from the previous example, except for removing the http binding. Here is the code required to create and initialize the ServiceHost.
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(
typeof(StringReverser),
new Uri[]{new Uri("net.pipe://localhost")}))
{
host.AddServiceEndpoint(typeof(IStringReverser),
new NetNamedPipeBinding(), "PipeReverse");
host.Open();
Console.WriteLine("Service is available. " +
"Press <ENTER> to exit.");
Console.ReadLine();
host.Close();
}
}
The real code changes come in initializing a client connection, however they are minor. Originally we were using a ChannelFactory to create our proxy to the server, however ChannelFactory doesn't support duplex communication. We're going to have to upgrade to a DuplexChannelFactory.
static void Main(string[] args)
{
Callbacks myCallbacks = new Callbacks();
DuplexChannelFactory<IStringReverser> pipeFactory =
new DuplexChannelFactory<IStringReverser>(
myCallbacks,
new NetNamedPipeBinding(),
new EndpointAddress(
"net.pipe://localhost/PipeReverse"));
IStringReverser pipeProxy = pipeFactory.CreateChannel();
while (true)
{
string str = Console.ReadLine();
Console.WriteLine("pipe: " +
pipeProxy.ReverseString(str));
}
}
This attachment is hidden for guests. Please log in or register to see it.