Publish-Subscribe is a common design pattern that is widely used in client/server communication applications. In WCF service development, the Publish-Subscribe pattern will also help in those scenarios where the service application will expose data to certain groups of clients that are interested in the service and the data is provided to clients as a push model actively (instead of polling by the client). This recipe will demonstrate how to implement the Publish-Subscribe pattern in a WCF service through dual binding.
The Publish-Subscribe pattern is widely adopted in various application development scenarios and there are many different kinds of descriptions for this pattern. Refer to the following links for more information:
- Publish/subscribe
- Observer pattern
To implement the Publish-Subscribe pattern, we need to put particular customization in various parts of the WCF service development, including a ServiceContract
design, binding configuration, and service hosting/consuming code. Let’s go through the following steps to build a typical list-based Publish-Subscribe service:
- The first thing to do is to design a
ServiceContract
that supports Publish-Subscribe pattern. The followingIPublicBillboard
interface represents a sample contract for publishing some announcements to client applications that are interested in the service.[ServiceContract(SessionMode=SessionMode.Required, CallbackContract=typeof(IClientReceiver))] public interface IPublicBillboard { [OperationContract(IsInitiating=true)] void Subscribe(); [OperationContract(IsTerminating=true)] void Unsubscribe(); [OperationContract(IsOneWay=true)] void Announce(string msg); } public interface IClientReceiver { [OperationContract(IsOneWay = true)] void GetAnnouncement(string msg); }
In the previous
ServiceContract
, in addition to the main contract interface, we also need to supply aCallbackContract
type, which is used for the service to notify the client proactively. Also, a session has been enabled here so that the service can conveniently identify client callback channels through theirsessionId
. - When implementing the
ServiceContract
,Subscribe
will cache the callback channel via the clientsessionId
so that it can notify the client later (by invoking the callback channel).[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] public class PublicBillboard : IPublicBillboard { object _syncobj = new object(); Dictionary<string, IClientReceiver> _receivers = new Dictionary<string, IClientReceiver>(); public void Subscribe() { string sessionId = OperationContext.Current.SessionId; lock (_syncobj) { if (_receivers.Keys.Contains(sessionId)) { _receivers.Remove(sessionId); } _receivers.Add(sessionId, OperationContext.Current.GetCallbackChannel<IClientReceiver>()); } }
The
Subscribe
method mentioned earlier, gets the clientsessionId
fromOperationContext
and caches the client’s callback channel by thissessionId
. If there is already a callback interface with the samesessionId
cached, the code will remove the existing one first. - In the service endpoint configuration, it is important to select a duplex binding. The sample service here uses WSDualHttpBinding through a configuration file.
- At the client side, we need to provide a type that implements the callback interface and supply this type instance when initializing the service proxy, which is done as follows:
public partial class MainForm : Form, BillboardProxy.IPublicBillboardCallback { BillboardProxy.PublicBillboardClient _boardclient = null; private void MainForm_Load(object sender, EventArgs e) { // Subscribe for the Billboard service _boardclient = new BillboardProxy.PublicBillboardClient(new InstanceContext(this) ); _boardclient.Subscribe(); btnSubmit.Enabled = true; } // Implement the callback interface void BillboardProxy.IPublicBillboardCallback.GetAnnouncement(string msg) { UpdateText(msg); }
The
MainForm
class implements the callback interface and is assigned to the client proxy (through a constructor) at the loading stage. - In the end, the service operation can selectively notify some or all of the clients proactively (refer to the
Announce
operation shown as follows):public void Announce(string msg) { // Enumerate all the client callback channels foreach (string key in _receivers.Keys) { IClientReceiver receiver = _receivers[key]; receiver.GetAnnouncement(string.Format(“{0} announced: {1}”, sessionId, msg)); } }
The service class is decorated as InstanceContextMode=InstanceContextMode.Single
, so that all the clients will share the same service instance. Enabling the session makes the service be able to differentiate clients by their sessionId
. The following screenshot shows the service console, which prints out all new announcements received from the client.

By invoking the Callback channel interface (from each client OperationContext
), the service proactively pushes the new announcement data to all clients immediately.
At the client side, the callback function simply updates the Forum UI and prints the new announcement in the textbox control.

Both the service callback operations are marked as one-way style. This is to make sure that the service operation won’t block the client UI thread when directly invoked in the Button_Click
event (which is also executed in the UI thread).
public interface IPublicBillboard { [OperationContract(IsOneWay=true)] void Announce(string msg); public interface IClientReceiver { [OperationContract(IsOneWay = true)] void GetAnnouncement(string msg);
If you are interested, you can also use asynchronous service execution to avoid these kinds of thread blocking issues.
- Invoking async operation via ChannelFactory in Chapter 5
- Complete source code for this recipe can be found in the
\Chapter
2\recipe3\
folder