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.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 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.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.
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 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 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.
{
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.
{
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));
}
}
The first thing I do is create an instance of my Callback class to handle the server's events. Next I simply create a DuplexChannelFactory object. The constructor is nearly identical to the ChannelFactory used in the previous tutorial, except now it excepts an object that will be handling the callbacks. Everything after this point is exactly the same as before.
Now when the client calls ReverseString the callback will be raised before the function returns, so the console output will look something like below when the client sends the string "My Reversed Text".
pipe: txeT desreveR yM
That does it for callbacks and events using WCF. You can download a fully functional server and client as Visual Studio 2008 projects here. And as always, questions and comments are welcome.
05/26/2009 - 05:32
I have a requirement like I have an admin app and client app both consuming same WCF service say (http://localhost:9002/Duplex.Service). If admin change something in the database through service client should get notified. I would like to know is there way to do this using duplex communication. The way you explained here keeping session by each app and callback will get only to same session
05/26/2009 - 09:10
The recommended way to do this is to keep a collection of callback interfaces and use a subscribe/unsubscribe model to manage them. Create a method in the communication interface called Subscribe. When clients connect, they should then call this function to let the service know they're interested in receiving callbacks. On the service side, pull the callback interface from the OperationContext in this function and store it in a collection somewhere. Then, when the service needs to perform a callback, it simply iterates through each callback interface in the collection and sends the message.
10/15/2009 - 01:31
As you say "In your previous tutorial you 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 you have removed the http bindings." Now i want to know how to create named pipe with the " IP Addresses and ports " instead of "localhost" ??
11/04/2009 - 06:50
I suspect you would be better off using just tcp rather than http. In the example below replace localhost with the ip address eg net.tcp://192.168.1.1:8000
, new Uri[] { new Uri("net.tcp://localhost:8000")});
host.AddServiceEndpoint(typeof(IHelloService), new NetTcpBinding(), "net.tcp://localhost:8000");
host.Open();
11/05/2009 - 07:14
good yar.
11/06/2009 - 10:33
really good one. thanks for the chef.
11/25/2009 - 17:31
Just what I needed, thanks
12/30/2009 - 14:41
Thank you for the tutorials. It would be nice if the background for the code of the server and client would differ in some way, so one can easily distinguish between both.
01/06/2010 - 01:16
thanks for the tutorial
01/07/2010 - 10:51
Excellent tutorials. Is there a somewhat easy way to secure the duplex communication over, say, SSL?
02/15/2010 - 11:27
Thanks so much - these were very helpful. Re: using NetTcpBinding, I found it would not work over the internet until I added a securitymode for it in the constructor in both the client and server. Otherwise I was getting 'connection actively refused' in the client when the callback code fired on the server.
06/14/2010 - 04:53
First of all this is a great article. Simple and clear. I have a situation where I use a WCF to make API calls to a third party API and expose them to WCF clients. To make a call to the API it needs to create a session first. In one instance I need to make about 100 calls which takes a little while to complete and it makes the client time out. I can open and close the session for each API call but it is not very efficient. Is it correct to use a event based approch to achive my goal. I place all 100 request from the client to the WCF service and wait for them to complete. As and when the WCF service receive results from the API it calls the call back method on the clients. Please adivice.
Thanks
Vijay
07/13/2010 - 22:46
I have a requirement where there could be multiple subscribers.
A) Would the collection of callback be static member or instance member? With instance mode set to PerSession static collection would cause duplicate messages to subscribers and private would not cause callback on every subscriber.
B) Should the instance context mode set to Singleton?
C) Is session really required for callback method is defined oneway?
Thanks
07/26/2010 - 19:52
Thanks for the great article. I was able to implement a WCF solution in a few hours. Very simple, clear and concise. Thanks much. Eric
01/13/2011 - 06:35
Thanks for the tutorial. Question
How to do if you have several clients and all have a callback method?
01/13/2011 - 10:51
The recommended practice is to add a 'register' function to the Service Contract (IStringReverser). When a client registers for callbacks, you get the Callback Channel and put it in a collection somewhere. Whenever the server wants to raise a callback, it simply iterates over the collection of callback channels and raises the callback on each client individually.
From the tutorial:
"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."
04/28/2011 - 09:08
I may just add that the list of ICallbacks should be stored as static (c#) // Shared (vb) or it will be wiped out in beetwen calls.
Other solutions exist not to loose that list.
01/29/2011 - 12:48
mind blowing dude. good job!
02/09/2011 - 09:00
Thank you for the tutorial, but i'm still having some problems.
Your sample project works, but when i try to put the code into my project it stops. I'm using a WPF project and i modified the code from your previous tutorial to make it work for me. I added a few more functions to the manager, and increased MaxReceivedMessageSize and MaxArrayLength because i am sending larger amounts of data. Other than that it's pretty much the same. But when i add a callback to a function and try to run it, even in your ReverseString function, my client app locks up and after the default timeout ( 1min. ) it places a "this is the next statement to execute when this thread returns" message at the client callback function. Even after that the client app doesn't crash, it just freezes.
I'm thinking it's got something to do with WPF, and tried googling it but with no success.
Any idea what i'm missing here?
Thanks.
02/10/2011 - 01:56
Ahhhh, after reading the comments in your "WPF Callbacks Hanging" article i got a solution to my problem.
I should have seen it before!
02/24/2011 - 15:30
#1)Familiar ~~ with duplex,,
#2) Greatly Appreciate the WORKING SIMPLE tutorial that avoids CONFIGURATION FILES !!!!! OMG THANK YOU .
Didn't download any code just built them both as I read the articles. Everyone knows how hard that is when you start naming your classes and tidbits with your own names.
Your risking a huge waste of your time when it finally doesn't work at the end. But this was concise and straight forward.
Now to step up to the Pub Sub & the Beer party example with a success under my belt again is encouraging.
Thanks
03/09/2011 - 11:50
Can I listen to a WCF event from a web client? Is this possible? I am not talking about call backs, I want the WCF service to raise and event and the web client to be able to listen. Is there a good example of this in C#?
04/27/2011 - 15:37
Thank-you Thank-you Thank-you for keeping this simple.
07/05/2011 - 13:49
I'd like to add an intel to this very interesting article :
The question i had to answer was : how to ensure the WCF cession beetween server and client is still alive ? there's always the solution to ping the server (heartbeat) but... seemed like reinvent the wheel. I was afraid by some microsoft post talking about InstanceContext beeing required for events to rise, or some difficult SOAP error message handling.
The answer, hard to find, turned out simple : cast the client into a ICommunicationObject and Add event handler to the event of your choice : Closed, Closing, Faulted, ...
In vb the code would be like (for duplex and Net pipe) :
[vb]
MyChannel = New DuplexChannelFactory(Of ISomething)(New CallBack, New NetNamedPipeBinding(), New EndpointAddress("net.pipe://localhost/ServiceName"))
MyProxy = MyChannel.CreateChannel()
Dim MyCommunicationObject As ICommunicationObject = TryCast(MyProxy, ICommunicationObject)
'then add handler
AddHandler MyCommunicationObject.Faulted, AddressOf TryReconnectToProxy
' the TryReconnectToProxy sub beeing like :
Private Sub TryReconnectToProxy(ByVal sender As Object, ByVal e As System.EventArgs)
' in the proxy, you should remove event handler, recreate the channel, and then add handler on the new proxy
end sub
[/vb]
Hope this helps !
Add Comment
[language] [/language]
Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]
See here for supported languages.
Javascript must be enabled to submit anonymous comments - or you can login.