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: wcf netfx3 callbacks events
links: digg this del.icio.us technorati reddit