The World’s Leading Microsoft .NET Magazine
   
 
The .NET Addict's Blog

My Top Tags

                                                           

My RSS Feeds








I heart FeedBurner

Latest Diggs - Programming

Computers Blogs - Blog Top Sites

Site Hits

Total: 3,822,723
since: 19 Jan 2005

Using Callback Contracts in WCF for Asynchronous Publish/Subscribe Event-Style Communication

posted Thu 03 Aug 06
For the most part, there are plenty of samples floating around the Internet and even a couple in some published books (though those are extremely out of date) that illustrate how to invoke methods on a service using WCF. The sample you typically see most often is this:

int result = calculatorService.Add(2,3);

WooHoo! We can now remotely add the numbers two and three and return the result. In case you hadn't noticed, I'm not a very big fan of "Hello World" style samples. I don't think they provide much value to the reader other than insulting their intelligence. While the model of invoking a method that returns some value that is hosted on a remote service is sufficient for a large majority of the WCF usage scenarios - it doesn't cover everything.

Suppose you have a WCF service running on a server and you've got some standard data retrieval methods like GetThis() or SearchForThat(), etc. What do you do if you want the server to tell your client as soon as another client adds a new row of data to the system. For example, you might have a service that provides the back-end for a technical support trouble ticket management system. You want your client applications to be notified as soon as a new trouble ticket is entered into the system. In traditional "ask and you shall receive" method invokation scenarios, you would have to set up a timer and periodically poll the server with a GetNewTroubleTickets() method. Unfortunately, this is horribly inefficient and depending on the requirements of the system, might not even be an option.

Back in the "good old days" of Remoting, you could attach your client to an instance of a remotely hosted MarshalByRefObject and simply subscribe to events published by that object. Unfortunately that model doesn't exist in WCF (with good reason...don't even get me started on how many apps Remoted events broke!). However, in WCF you can implement something called a CallbackContract. Basically, in the definition of a service contract, you can also specify an interface to be used as a callback. A callback interface is basically a guarantee that anyone calling this particular service is also hosting their own mini-service that implements the callback contract type.
Don't worry if you don't see all the code for this in the article, there will be a link to download the code sample at the end of this post.
To see this in action, here are the interface definitions for the IAlarmServer (the alarm clock itself) and the IAlarmCallback contract interfaces.

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
namespace ContractsLibrary
{
    [ServiceContract(
        Name="AlarmServer",
        Namespace="http://dotnetaddict.dotnetdevelopersjournal.com/wcf.samples",
        CallbackContract=typeof(IAlarmCallback),
        SessionMode=SessionMode.Required)]
    public interface IAlarmServer
    {
        [OperationContract(IsOneWay = true)]
        void RegisterAlarm(DateTime alarmTime, string clientName, string reminderMessage);
    }
}

And this is the callback contract that will be implemented by a class on the client:

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;

namespace ContractsLibrary
{
    public interface IAlarmCallback
    {
        [OperationContract(IsOneWay = true)]
        void SignalAlarm(string reminderMessage);
    }
}

A couple of othings worth noting here: We are making use of WCF sessions (the session is what provides the ability for each client to have its own instance of the object rather than having the instance mode set to PerCall or Single (Singleton). You'll see the instance context mode set more explicitly in the server-side implementation of IAlarmServer:
using System;
using System.Timers;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using ContractsLibrary;

namespace AlarmHost
{
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
    public class AlarmServer : IAlarmServer
    {
        private RegisteredAlarm alarm = null;
        private Timer _timer;

        public AlarmServer()
        {           
            _timer = new Timer();
            _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
            _timer.Interval = 60000; // 1 minute
            _timer.Start();
        }

        private void _timer_Elapsed(object sender, ElapsedEventArgs e)
        {                       
            if ((alarm.AlarmTime <= DateTime.Now) && !alarm.Fired)
            {
                alarm.Callback.SignalAlarm(alarm.Message);
                alarm.Fired = true;
            }
        }
        #region IAlarmServer Members

        public void RegisterAlarm(DateTime alarmTime, string clientName, string reminderMessage)
        {
            alarm = new RegisteredAlarm();
            alarm.AlarmTime = alarmTime;
            alarm.ClientName = clientName;
            alarm.Message = reminderMessage;
            alarm.Callback = OperationContext.Current.GetCallbackChannel<IAlarmCallback>();
           
            System.Diagnostics.Debug.WriteLine("Alarm registered for client " + clientName + ".");
        }

        #endregion
    }
}

One line of code from the preceding listing sticks out:

alarm.Callback.SignalAlarm(alarm.Message);

That line of code is actually making a call from the server to the client using the stored reference to a proxy-based implementation of IAlarmCallback. This is an extremely powerful ability, and its easy to read, easy to implement, and just plain cool.
Now that you have the server implemented, you need to create an app.config to host the server information. Here's where I ran into trouble. Despite what people have told me on the forums, I cannot get the TCP binding to provide me with meta-data that svcutil.exe can use. So, before I ended up with my final app.config I created one that used wsDualHttpBinding binding, generated my proxy, and then switched it back to netTcpBinding as shown in the app.config below:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="MetaEnabledBehavior" name="AlarmHost.AlarmServer">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:8080/AlarmServer"/>
          </baseAddresses>
        </host>
        <endpoint address="" binding="netTcpBinding" contract="ContractsLibrary.IAlarmServer"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MetaEnabledBehavior">
          <serviceDebug includeExceptionDetailInFaults="true"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>


Next, use svcutil.exe to generate the client proxy (remember,I had to be hosting the service in HTTP mode to get the metadata to come out right - but once you have the client proxy you can revert back to TCP). The client application consists of two main pieces. The main form responsible for instantiating the client proxy and invoking the RegisterAlarm() method with input from a DateTimePicker and 2 text boxes and the class that implements the IAlarmCallback interface.

**Huge Asterisk: You cannot implement the shared version of the interface. You MUST implement the version of the interface that is contained in the client proxy. I tried it a few dozen ways without and WCF never liked my configuration file or my instantiation of the service host. There may be a way to do this, but I couldn't figure it out in the limited time I had (working on chapters for a book leaves me with little time for code samples like this). Anyway, here's the IAlarmCallback implementation (note that the client proxy doesn't actually prefix interfaces with an I, way to stick to your own design convention guys!):

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace AlarmClient
{
    public class AlarmCallback : AlarmServerCallback
    {
        #region AlarmServerCallback Members

        public void SignalAlarm(string reminderMessage)
        {
            MessageBox.Show("The server would like to remind you: " + reminderMessage);
        }

        #endregion
    }
}


And here's how to instantiate the service and the event handler on the main form that invokes the remote method:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;

namespace AlarmClient
{
    public partial class MainForm : Form
    {
        private AlarmServerClient client = null;
        public MainForm()
        {
            InitializeComponent();
            InstanceContext context = new InstanceContext(new AlarmCallback());
            client = new AlarmServerClient(context);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            client.RegisterAlarm(dateTimePicker1.Value, clientName.Text, reminderMessage.Text);
            MessageBox.Show("Alarm registered with server.");
        }
    }
}

When all is said and done, you get a screen that looks like this. (I didn't show it inline because its pretty large). Note that in the screenshot you can see that I have two different clients running against the same server - each of these has their own session and has registered their own Callback Channel with the service
And here's the file download link: Click to download the sample.

tags:        

links: digg this    del.icio.us    technorati    reddit

AddThis Social Bookmark Button




1. Daniel left...
Tue 19 Sep 06 4:05 pm

Hi there, instead of a comment, this is more of a question. I was wonder how did you get the netTcpBinding to work on your computer. Are you working on Windows Vista, or some other server? I'm on a Windows XP pro and i get this message that says that the protocol is not supported. So i was wondering how did you get it to work? By the way, the example is very good! Very different from the "HelloWorld" examples that are out there. Keep it up! ;)


2. Lee left...
Thu 28 Sep 06 11:41 pm

I found the this little batch file handy for calling svcutil and renaming the output files, just change the bracketed entries to what's relevant. Once it's executed copy the output files to the client project folder.

"C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\svcutil.exe" http://localhost:<port>/<Address>/<ServiceName>.svc?wsdl

del <ServiceName>Proxy.cs

del App.config

ren <ServiceName>.cs <ServiceName>Proxy.cs

ren output.config App.config


3. Sandeep left...
Tue 14 Nov 06 3:29 am

Kindly provide me with the svcutil command along with options because i am stuck ,, tryin to generate WCF client


4. Sandeep left...
Tue 14 Nov 06 3:33 am

while generating the client code i get an error saying WS-Metadata exchange error.

Metadata contains a reference that cannot be resolved

Kindly help me

Thanks in advance


5. Chris D left...
Tue 16 Jan 07 2:24 pm

Has anyone gotten this sample to work? I dowloaded the code, ran the host and the clent, registered an alarm (saw the messagebox indiciating the alarm was registered), but the callback never seems to fire. Any one else have a similar experience?


6. Jamel left...
Fri 19 Jan 07 6:17 pm

Kevin, Do you know if WCF Callbacks work over NetNamePipeBinding? I am unable to download your code sample to try it out. Thanks for your time.


7. talasal left...
Mon 22 Jan 07 8:11 pm

if i want to use the netmsmqbinding to pub/sub message ,,how can i do it? netmsmqbinding is one-waycannt callback so can use two service to solute the problem? thanks


8. P left...
Tue 22 May 07 3:44 pm

in regards to not being able to run the svcutil at nettcp binding-- i had to create a second endpoint with mexhttpBinding (contract is imetadataexchange), on the host and run the svcutil at that -- it uses that endpoint to generate but still communicates with the nettcp because that is what i am using for the clients.


9. Joe left...
Thu 24 May 07 4:41 pm

Great sample. Just what I was looking for.


10. Ryan Terry left...
Sun 20 Jan 08 7:34 pm

How did you generate the auto generated proxy?


11. Kevin Hoffman left...
Sun 20 Jan 08 9:46 pm

You can't generate the client with regular TCP binding, so I used HTTP binding on the server, generated the client using the command-line utility, and then reverted the channel back to TCP.


12. Ryan Terry left...
Mon 21 Jan 08 4:34 pm

I'm a beginner at using WCF, so please bear with me. I need callback for a project im working on, and im not sure how to go about doing what you just said. Can this all be done on one machine?


13. Scott left...
Fri 02 May 08 12:26 pm

Works great, thanks. I have to wonder though what the lifetime is on the callback proxies. In a long running process, will the session timeout rendering the callback un-usable?


14. Kevin Hoffman left...

Its a good question. Really what you would want to do is build the services so that they are stateless and can be re-instantiated by the WCF platform at will. Basically this means making sure that the service isn't maintaining the state, the application is, and the service is really a broker between the remote end and the local application. I usually accomplish this with events and a pub/sub pattern.


15. Scott left...
Wed 07 May 08 8:55 am

Thanks for the reply. I'm not sure I really understand how to accomplish it though.

I tested the callback instance lifetime by writing a sample service that keeps a static list of callback proxies. The service is setup as a singleton using InstanceContextMode = InstanceContextMode.Single. It works great as long as I keep the callback references alive by using them, but if I let it run overnight and try it the next day, they have all timed out. I don't see any config settings thet look like they would help me. Do you have any suggestions?

Thanks, Scott


16. Magnus left...
Sun 15 Feb 09 1:54 pm

I think you got it wrong about the following: Quote "Anyway, here's the IAlarmCallback implementation (note that the client proxy doesn't actually prefix interfaces with an I, way to stick to your own design convention guys!)" This is because you explicitly renamed it yourself when you created the IAlarmServer infterface. When you specified the named parameter 'Name' for the ServiceContractAttribute you effectively renamed the interface name from just IAlarmServer to AlarmServer yourself. So this has nothing to do with MS conventions.


Tag Related Posts

Building RESTful Java Web Services with JAX-RS

Mon 23 Feb 09 8:42 P GMT-05
tags:                    

Know thine Enemy: RESTful Web Services in ... Java!

Sun 11 Jan 09 2:08 P GMT-05
tags:                    

CLINQ v1 Demo - Network Message Filtering

Wed 09 Jan 08 7:47 P GMT-05
tags:        

Orcas' Hidden Gem - The managed PNRP stack

Fri 11 May 07 6:45 P GMT-05
tags:        

On MUDs

Thu 08 Mar 07 5:00 A GMT-05
tags:                    

What I think is a bug in WCF POX messaging

Thu 04 Jan 07 4:58 P GMT-05
tags:      

WPF Bumper Stickers

Tue 12 Dec 06 7:32 P GMT-05

Ulysses Agenda Makes Redmond Developer News

Wed 29 Nov 06 7:10 P GMT-05
tags:                

Ulysses Agenda - Network Engine Test 1

Mon 09 Oct 06 2:26 A GMT-05
tags:              

Ulysses Agenda : First Cut Networking Design

Thu 14 Sep 06 12:46 A GMT-05
tags:                  

First Impressions of Windows Vista RC1

Thu 07 Sep 06 1:30 P GMT-05
tags:                      

Localizing a WPF Application

Tue 22 Aug 06 11:39 A GMT-05
tags:            

WPF Slide Show and Photo Album

Fri 18 Aug 06 6:48 P GMT-05

Is Windows Workflow Foundation Too Complex?

Fri 18 Aug 06 12:15 P GMT-05

ADO.NET Entity Framework Announced Today!

Wed 16 Aug 06 11:08 A GMT-05

Extending your Applications with MAF/VSTA - Part III

Fri 21 Jul 06 12:30 A GMT-05
tags:            

Extending your Application with MAF/VSTA AddIns

Thu 20 Jul 06 1:51 A GMT-05
tags:            

July CTP is out... yay.

Tue 18 Jul 06 6:34 P GMT-05
tags:    

.NET Framework 3.0 June CTP is out!

Fri 23 Jun 06 6:23 P GMT-05
tags:                

Tech-Ed 2006 - Session Reviews

Tue 13 Jun 06 6:22 P GMT-05
tags: