WCF Callback Hooks

In order to build asynchronous callbacks from a WCF service to client, you will need to add some hooks to your existing WCF Service, Host, and Client. Here is a list of hooks that you can implement to get a rough idea as to how to build service callbacks in your application.
Download the example here.

Example Model

This example is a three-tier model which consists of:

  1. The Service Library (WcfServiceLibrary1.Service1 and WcfServiceLibrary1.IService1). This library represents the business logic and the WCF contracts and operations.
  2. The Service Host. In this example the host was provided by IIS. However, to implement WCF callbacks, the only hook you need to worry about on the host is making sure you use a duplex binding.
  3. The Client (WcfClient.Form1 and WcfClient.Callback)

The WCF Callback Hooks

  1. The service needs to create an additional contract (interface) that describes the callback method.
    namespace WcfServiceLibrary1
    {
        //The contract that defines the callback operation
        interface ICallback
        {
            //Decorate the contract with an OperationContract attribute and specify the operation as One-way method.
            [OperationContract(IsOneWay = true)]
            void DoCallback(string message);
        }
    }
    

    Note: If you do not specify the IsOneWay=true parameter you run the risk of receiving the exception;

    System.TimeoutException: This request operation sent to [the service location] did not receive a reply within the configured timeout (00:00:59.9940000). The time allotted to this operation may have been a portion of a longer timeout. This may be because the service is still processing the operation or because the service was unable to send a reply message. Please consider increasing the operation timeout (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property) and ensure that the service is able to connect to the client.

  2. The client will create a class that implements the service’s callback interface.
    namespace WcfClient
    {
        //This client class must implement the services callback contract
        public class Callback : ServiceReference1.IService1Callback
        {
            //This is the method that the service will call when it makes a call back to the client
            public void DoCallback(string message)
            {
                //In this example, the message is supplied by the service
                System.Windows.Forms.MessageBox.Show(message);
            }
        }
    }
    
  3. The main service contract will:
    1. Associate the callback contract with itself
      namespace WcfServiceLibrary1
      {
      //Associate the callback contract with this contract
      [ServiceContract(CallbackContract=typeof(ICallback))]
      public interface IService1
      {
      ....
      
    2. Create an operation that forces the service to support the callback method (the client will call this method).
      namespace WcfServiceLibrary1
      {
      [ServiceContract(CallbackContract=typeof(ICallback))]
      public interface IService1
      {
      [OperationContract]
      string GetData(int value);
      
      //Define the callback method
      [OperationContract(IsOneWay = true)]
      void Callback();
      }
      }
      
  4. The service will need an operation (method) that the client will call via the service proxy.
    namespace WcfServiceLibrary1
    {
        public class Service1 : IService1
        {
            //Operations that are available to the client via the service proxy
            public string GetData(int value)
            {
                return string.Format("You entered: {0}", value);
            }
    
            //This is the method the client will call via the service proxy
            public void Callback()
            {
                //To callback to the client, the service needs a reference to that client.
                //Create the channel that the service uses to callback to the client with
                //The magic of the GetCallbackChannel<T>() is possible because the client specified the
                //channel when it instantiated the service via the InstanceContext() method.
                ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>();
    
                //
                //Here is where you put the work that the service is going to do prior to sending the callback to the client
                //
    
                //Pause for a few seconds to simulate work
                System.Threading.Thread.Sleep(5000);
    
                //Get the current time to show work took some time.
                DateTime now = DateTime.Now;
    
                //Notify the client that the service completed its work.
                callback.DoCallback(string.Format("I am the WCF service letting you know I did something.\nTime: {0}", now.ToString("MMM d h:mm:ss")));
            }
        }
    }
    
  5. The service must use a binding that supports duplex communication. Here’s an example of defining the wsDualHttpBinding binding in the .config file of the service host.
          <service name="WcfServiceLibrary1.Service1">
            <endpoint address="" binding="wsDualHttpBinding" contract="WcfServiceLibrary1.IService1">
              <identity>
                <dns value="localhost" />
              </identity>
            </endpoint>
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
          </service>
    

    Note the client’s .config file must use the same binding as the service host!

  6. The client will specify context and channel information, then call the service’s callback operation
    namespace WcfClient
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                //Instantiate the class that handles the callback from the service
                Callback callback = new Callback();
    
                //Remember that in order for the service to callback to the client, the service needs a reference to the client!
                //Here is where the client sends context information to the service that allows the service to make the callback.
                //The InstanceContext stores context information and references to the channels that are being used.
                var context = new InstanceContext(callback);
    
                //Instatiate the service proxy and pass the context to the constructor
                ServiceReference1.Service1Client proxy = new ServiceReference1.Service1Client(context);
    
                //Call the operation that has no callback implementation
                string response = proxy.GetData(DateTime.Now.Millisecond);
                this.label1.Text = response; //Display the service response
    
                //Call the operation that has a callback
                proxy.Callback(); //The callback will be handled by the client's Callback class
            }
        }
    }
    

    Note that the call to proxy.Callback() is asynchronous which means that the client can do something else while the service is doing the callback work. In other words, the client doesn’t have to wait around for the service to make the callback!

Leave a comment