Monday, 6 May 2013

Interprocess Communication with WCF - Part 2

In Part 1 I showed you how you can use WCF with named pipes to send messages from a client to a server. Now I will show you how we can modify that code to send messages in the other direction, from the server back to the client. We do this by creating a callback interface and referencing it in our service contract. We then implement the callback interface in our client.

First things first, lets create our callback interface. In our server application add a new class file, ICallback.cs. This is just going to be a very simple interface where we declare the one method which our server is going to use to send messages to our client. It looks like this.

  1. using System.ServiceModel;
  2.  
  3. namespace Server
  4. {
  5.     public interface ICallback
  6.     {
  7.         [OperationContract(IsOneWay = true)]
  8.         void SendMessage(string message);
  9.     }
  10. }

Very simple and straight forward but note we need to decorate any methods of our callback interface that we wish to expose to the server with an OperationContract attribute. You may notice on the OperationContract attribute I have set the IsOneWay property to true, I will explain this later on in the tutorial.

Next step, in our client application we're going to implement our callback interface so add a class file, Callback.cs. We're not doing anything clever in here, we're just going to show a MessageBox containing the message sent from the server. Here's the callback implementation.

  1. using System.Windows.Forms;
  2. using Server;
  3.  
  4. namespace Client
  5. {
  6.     public class Callback : ICallback
  7.     {
  8.         public void SendMessage(string message)
  9.         {
  10.             MessageBox.Show(message);
  11.         }
  12.     }
  13. }

Next we're going to modify our service implementation to send a message to our client immediately after displaying the message sent by the client. It should now look like this.

  1. using System.Windows.Forms;
  2. using System.ServiceModel;
  3.  
  4. namespace Server
  5. {
  6.     public class HelloWorldService : IHelloWorldService
  7.     {
  8.         public void SendMessage(string message)
  9.         {
  10.             MessageBox.Show(message);
  11.             ICallback callback = OperationContext.Current.GetCallbackChannel<ICallback>();
  12.             callback.SendMessage("World says hello!");
  13.         }
  14.     }
  15. }

As you can see from this piece of code, to access the channel to the client so we can call methods on it we need to use the current OperationContext and call the generic GetCallbackChannel method. We are then able to call any of exposed methods of our callback interface.

Next we're going to modify our service contract so that it references our callback interface. We're also going to modify the OperationContract to set IsOneWay to true. Our service contract now looks like this.

  1. using System.ServiceModel;
  2.  
  3. namespace Server
  4. {
  5.     [ServiceContract(CallbackContract = typeof(ICallback))]
  6.     public interface IHelloWorldService
  7.     {
  8.         [OperationContract(IsOneWay = true)]
  9.         void SendMessage(string message);
  10.     }
  11. }

To reference the callback interface we simply modify the ServiceContract attribute and set the Callback property to the type of our callback interface. I will now explain the reason for using IsOneWay on the OperationContract here and in the callback interface.

In WCF, even when you declare a method as void, a response message is returned to the party invoking the method. Because we are invoking the callback to our client from within the method invoked by the client we would enter a deadlock situation.

The client would be waiting for a response from server and the server would be trying to invoke the callback on the client before sending back the response. The answer is to mark both methods as IsOneWay, this stops a response message from being generated and avoids deadlock.

Finally, we need to modify our client's button click handler. It will now look like this.

  1. using System;
  2. using System.Windows.Forms;
  3. using Server;
  4. using System.ServiceModel;
  5.  
  6. namespace Client
  7. {
  8.     public partial class Form1 : Form
  9.     {
  10.         public Form1()
  11.         {
  12.             InitializeComponent();
  13.         }
  14.  
  15.         private void button1_Click(object sender, EventArgs e)
  16.         {
  17.             Callback callback = new Callback();
  18.             InstanceContext context = new InstanceContext(callback);
  19.             DuplexChannelFactory<IHelloWorldService> pipeFactory =
  20.                 new DuplexChannelFactory<IHelloWorldService>(context,
  21.                 new NetNamedPipeBinding(),
  22.                 new EndpointAddress("net.pipe://localhost/HelloWorld"));
  23.  
  24.             IHelloWorldService pipeProxy = pipeFactory.CreateChannel();
  25.  
  26.             pipeProxy.SendMessage("Hello World!");
  27.         }
  28.     }
  29. }

The main change here is we are now using the generic DuplexChannelFactory<TChannel> class instead of ChannelFactory<TChannel>. Two way communications require duplex channels and these require a client to provide a channel for the service to send messages back through.

We do this in the constructor of DuplexChannelFactory by passing through an extra parameter, an InstanceContext. We instantiate the InstanceContext with an instance of our callback implementation. This object is now the client channel we will receive our server messages through.

There we have it, two-way interprocess communication in WCF using named pipes! You can download the source code here.

No comments:

Post a comment