Building a Publish-Subscribe service with dual binding

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.

Getting ready

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:

How to do it...

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:

  1. The first thing to do is to design a ServiceContract that supports Publish-Subscribe pattern. The following IPublicBillboard 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 a CallbackContract 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 their sessionId.

  2. When implementing the ServiceContract, Subscribe will cache the callback channel via the client sessionId 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 client sessionId from OperationContext and caches the client’s callback channel by this sessionId. If there is already a callback interface with the same sessionId cached, the code will remove the existing one first.

  3. In the service endpoint configuration, it is important to select a duplex binding. The sample service here uses WSDualHttpBinding through a configuration file.
    How to do it...
  4. 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.

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

How it works...

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.

How it works...

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.

How it works...

There’s more...

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.

See also

  • Invoking async operation via ChannelFactory in Chapter 5
  • Complete source code for this recipe can be found in the \Chapter 2\recipe3\ folder