WCF Tutorial - Events and Callbacks

Skill

WCF Tutorial - Events and Callbacks

Posted in:

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));
  }
}

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".

Callback Received: txeT desreveR yM
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.

Frank
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

reply

The Reddest
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.

reply

Jignesh
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" ??

reply

Void
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

ServiceHost host = new ServiceHost(typeof(HelloService)
, new Uri[] { new Uri("net.tcp://localhost:8000")});

host.AddServiceEndpoint(typeof(IHelloService), new NetTcpBinding(), "net.tcp://localhost:8000");

host.Open();

reply

shanka
11/05/2009 - 07:14

good yar.

reply

Anonymous
11/06/2009 - 10:33

really good one. thanks for the chef.

reply

Thomas
11/25/2009 - 17:31

Just what I needed, thanks

reply

Anonymous
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.

reply

Anonymous
01/06/2010 - 01:16

thanks for the tutorial

reply

baggachipz
01/07/2010 - 10:51

Excellent tutorials. Is there a somewhat easy way to secure the duplex communication over, say, SSL?

reply

Seldon
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.

new NetTcpBinding(SecurityMode.None),

reply

Vijay
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

reply

JobLot
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

reply

E Lee
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

reply

David
01/13/2011 - 06:35

Thanks for the tutorial. Question

How to do if you have several clients and all have a callback method?

reply

The Reddest
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."

reply

Vincent Piel
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.

reply

Pawan
01/29/2011 - 12:48

mind blowing dude. good job!

reply

D
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.

reply

D
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!

reply

ChristianProgrammer
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

reply

Anonymous
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#?

reply

Marty Neal
04/27/2011 - 15:37

Thank-you Thank-you Thank-you for keeping this simple.

reply

Vincent Piel
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 !

reply

Add Comment

Put code snippets inside language tags:
[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.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.