WCF Duplex Communication Tutorial

01. December 2013 WCF 0

The client starts the connection to the service. After the client connects to the service, the service can call back into the client. Duplex communication can be done with the WsHttpBinding and the NetTcpBinding as well as by using WebSocket protocol.

Contract

For duplex communication a callback contract must be specified that is implemented in the client. If the operation in the callback interface has the operation contract setting IsOneWay=True, the service doesn’t wait until the method is successfully invoked on the client.

By default the service instance can be invoked from only one thread (ConcurrencyMode = ConcurrencyMode.Single). To get the reply from the client and avoid deadlock to be happened the ConcurrencyMode can be changed to Multiple or Reentrant:

  • With Multiple, the multiple thread can be accessed the instance of the service. In the case the locking should be implemented.
  • With Reentrant, the service instance stays single-threaded but enables answers from callback request to reenter the context.

Instead of changing concurrency mode, the IsOneWay property can be changed in the operation contract. In this way the caller does not wait for a reply.

The call back contract is mapped to the service contract with the CallbackContract property of the service contract definition.

  • Add an empty solution called DuplexCommunication
  • Add a WCF Service Library project to solution called MessageService
  • Add a class called IMyMessage
  • Modify the code as below:

IMyMessage.cs

using System.ServiceModel;
 
namespace MessageService
{
    public interface IMyMessageCallbak
    {
        [OperationContract(IsOneWay = true)]
        void OnCallback(string message);
    }
 
    [ServiceContract(CallbackContract=typeof(IMyMessageCallbak))]
    public interface IMyMessage
    {
        [OperationContract]
        void MessageToServer(string message);
    }
}

Service

The service writes the message from the client to the console. The OperationContext class can be used to access the call back contract. OperationContext.Current returns the OperationContext associated with the current request from the client. With the OperationContract, you can access Session Information, Message Headers and Properties, and The Callback Channel (in the case of duplex communication).

The generic method GetCallbackChannel returns the channel to the client instance. This channel can then be used to send message to client by invoking the method OnCallbak which is defined with the callback interface. A new thread for receiving the callback channel can be created to use the callback channel from the service independently of the completion of the method. The new thread sends messages to the client by using the callback channel.

  • Add a new class called MessageService to the project
  • Modify the code as below:

MessageService.cs

using System;
using System.ServiceModel;
using System.Threading.Tasks;
 
namespace MessageService
{
    class MessageService : IMyMessage
    {
        public void MessageToServer(string message)
        {
            Console.WriteLine("Message fromt the client: {0}", message);
 
            IMyMessageCallbak callback = OperationContext.Current.GetCallbackChannel<IMyMessageCallbak>();
            callback.OnCallback("Message from the server.");
 
            Task.Factory.StartNew(new Action<object>(TaskCallback), callback);
        }
 
        private async void TaskCallback(object callback)
        {
            IMyMessageCallbak messageCallback = callback as IMyMessageCallbak;
 
            for (int i = 0; i < 10; i++)
            {
                messageCallback.OnCallback("message " + i.ToString());
                await Task.Delay(1000);
            }
        }
    }
}
  • Modify the configuration binding to support a duplex channel.

App.config

…
<!-- Service Endpoints -->
<!-- Unless fully qualified, address is relative to base address supplied above -->
<endpoint address="" binding="wsDualHttpBinding" contract="MessageService.IMyMessage">
…

Host

  • Add a new Console Application project to the solution and name it DuplexHost.
  • Add a reference to MessageService dll.
  • Modify its App.config as bellow:

App.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
 
  <system.serviceModel>
    <services>
      <service name="MessageService.MessageService">
        <endpoint contract="MessageService.IMyMessage" binding="wsDualHttpBinding" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8733/Service1" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>
  • Add a reference to MessageService.
  • Modify the Program.cs as bellow:

Program.cs

using System;
using System.ServiceModel;
 
namespace DuplexHost
{
    class Program
    {
        internal static ServiceHost serviceHost = null;
 
        private static void StartService()
        {
            serviceHost = new ServiceHost(typeof(MessageService.MessageService));
            serviceHost.Open();
        }
 
        private static void StopService()
        {
            if (serviceHost.State != CommunicationState.Closed)
                serviceHost.Close();
        }
 
        static void Main(string[] args)
        {
            StartService();
            Console.WriteLine("Service running; press return to exit");
            Console.ReadLine();
            
            StopService();
            Console.WriteLine("Service Stoped");
        }
    }
}

Client Application

With the client application the callback contract must be implemented.

  • Add a new console application to the solution and name it MessageClient
  • Add a class called ClientCallback to the project and modify it as bellow:

ClientCallback.cs

using System;
using MessageService;
 
namespace MessageClient
{
    public class ClientCallback : IMyMessageCallbak
    {
        public void OnCallback(string message)
        {
            Console.WriteLine("Message from server, {0}.", message);
        }
    }
}
  • Modify Program.cs as bellow:

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
 
namespace MessageClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client - wait for service");
            Console.ReadLine();
 
            DuplexSample();
 
            Console.WriteLine("Client - press return to exit");
            Console.ReadLine();
        }
 
        private async static void DuplexSample()
        {
            var binding = new WSDualHttpBinding();
            var address = new EndpointAddress("http://localhost:8733/Design_Time_Addresses/Duplex");
            
            var clientCallback = new ClientCallback();
            var context = new InstanceContext(clientCallback);
 
            var factory = new DuplexChannelFactory<MessageService.IMyMessage>(clientCallback, binding, address);
 
            MessageService.IMyMessage messageChanel = factory.CreateChannel();
 
            await Task.Run(() => messageChanel.MessageToServer("Message from client."));
        }
    }
}

ChannelFactory cannot be used with duplex channel to initiate the connection to the service. For this purpose DuplexChannelFactory should be use. The constructor of this class has one more parameter in addition to binding and address configurations. This parameter specifies an InstanseContext that wraps one instance of our ClientCallback class. When passing this instance to the factory, the service can invoke the object across the channel. The client just needs to keep the connection open.

[Edited: 2nd July 2015]
Download the code sample.


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.