Introduction
At the beginning of this article, let's talk about the messaging transfer mode in WCF.
Messaging TransferMode
- Request-Reply - By default, all WCF will operated in Request-Reply mode. It means that, when client make a request to the WCF service and client will wait to get response from service (till receive Timeout, defaultl value is 1 minute, you can change it By ReceicedTimeOut object). If service doesn't responed to the service within received timeout, Client will receive TimeOutException.
- One-Way - In One-Way transfer mode, client will send a request to the server and does not care whether it is success or failure of service execution. These is no return from the server side, it is one-way commucation. Client will be blocked only for a moment till it dispatches its call to service. If any exception thrown by service will not reach the client.
Client will continue to execute its statement, after making one-way call to server. There is no need to wait, till server execute. But sometime when one-way calls reach the service, they may not be dispatched one at a time, according to the service's configured concurrency mode behavior. If the number of queued messages has excessed the queue's capacity, the client will be blocked even if it's issued a one-way call.
One-Way operation can be enabled by setting IsOneWay property to true in Operation cantract attribute.
[ServiceContract]
public interface IMyService
{
[OperationContract(IsOneWay=true)]
void MyMethod(string parameter);
}
You also can use the One-Way communication with Sessionful service.
[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
[OperationContract(IsOneWay = true)]
void MyMethod();
}
- Callback Service - Till now, we have seen that all cilents will call the service to get the things done. But WCF also provides the service to call the client. So we call it duplex transfer mode.
Callback Service
Note
-
HTTP protocols are connectionless nature, so it is not supported for callback operation. So BasicHttpBinding and WSHttpBinding cannot be used for callback service.
-
WSDualHttpBinding is made for call back operation.
-
All this bindings include TCP and IPC protocols support Duplex communication.
Step 1 : Configuring a callback contract
Indeed it define what operations the client should have.
namespace WCFTraining.DuplexStreamingService
{
public interface IPushCallback
{
[OperationContract(IsOneWay = true)]
void ReceiveData(string data);
[OperationContract(IsOneWay = true)]
void ReceiveStream(Stream stream);
[OperationContract(IsOneWay = true)]
void ReceiveLog(List<string> log);
}
}
Step 2 : Define a service and it's implement
Callback service can be enabled by using CallbackContract property in the ServiceContract attribute. Let's implement a difficult demo.
namespace IISHost.DuplexStreamingService
{
[ServiceContract(CallbackContract = typeof(IPushCallback))]
public interface IDuplexService
{
[OperationContract(IsOneWay = true)]
void StartPushingData();
[OperationContract(IsOneWay = true)]
void StopPushingData();
[OperationContract(IsOneWay = true)]
void StartPushingStream();
[OperationContract(IsOneWay = true)]
void StopPushingStream();
[OperationContract(IsOneWay = true)]
void GetLog();
}
}
Implement the operation in the service.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.Threading;
namespace WCFTrrainning.DuplexStreamingService
{
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Reentrant)]
public class DuplexService : IDuplexService
{
List<string> log = new List<string>();
static bool continuePushingData;
#region Duplex operation
public void StartPushingData()
{
log.Add("StartPushingData");
continuePushingData = true;
IPushCallback pushCallPackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>();
ThreadPool.QueueUserWorkItem(new WaitCallback(PushData), pushCallPackChannel);
}
public void StopPushingData()
{
log.Add("StopPushingData");
continuePushingData = false;
}
public void StartPushingStream()
{
log.Add("StartPushingStream");
IPushCallback pushCallbackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>();
ThreadPool.QueueUserWorkItem(new WaitCallback(PushStream), pushCallbackChannel);
}
public void StopPushingStream()
{
log.Add("StopPushingStream");
localStream.StopStreaming = true;
}
public void GetLog()
{
IPushCallback pushCallbackChannel = OperationContext.Current.GetCallbackChannel<IPushCallback>();
pushCallbackChannel.ReceiveLog(log);
}
private static void PushData(object state)
{
IPushCallback pushCallPackChannel = state as IPushCallback;
do
{
pushCallPackChannel.ReceiveData(CreateInterestingString(rand.Next(4, 256)));
} while (continuePushingData);
pushCallPackChannel.ReceiveData("Last Message");
}
void PushStream(object state)
{
IPushCallback pushCallbackChannel = state as IPushCallback;
localStream = new FlowControlledStream();
localStream.ReadThrottle = TimeSpan.FromMilliseconds(800);
pushCallbackChannel.ReceiveStream(localStream);
}
#endregion
}
}
the service will host with IIS. So we need to edit our web.config
<version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true">
</compilation>
</system.web>
<system.serviceModel>
<services>
<service name="IISHost.DuplexStreamingService.DuplexService">
<endpoint address="" binding="netTcpBinding" contract="IISHost.DuplexStreamingService.IDuplexService"></endpoint>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange"></endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
<system.webServer>
<directoryBrowse enabled="true" />
</system.webServer>
</configuration>
Client Sides
Step 3 : Implement the Callback contract
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using DuplexStreamingService;
namespace DuplexStreamingClient
{
public class ClientReceiver : IPushCallback
{
public ManualResetEvent LogReceived { get; set; }
public ManualResetEvent ReceiveDataInvoked { get; set; }
public ManualResetEvent ReceiveDataCompleted { get; set; }
public ManualResetEvent ReceiveStreamInvoked { get; set; }
public ManualResetEvent ReceiveStreamCompleted { get; set; }
public string Name { get; set; }
public List<string> ServerLog { get; set; }
public ClientReceiver()
{
LogReceived = new ManualResetEvent(false);
ReceiveDataInvoked = new ManualResetEvent(false);
ReceiveDataCompleted = new ManualResetEvent(false);
ReceiveStreamInvoked = new ManualResetEvent(false);
ReceiveStreamCompleted = new ManualResetEvent(false);
Name = "ClientReceiver_" + DateTime.Now;
}
public void ReceiveData(string data)
{
Console.WriteLine("[{0}] ReceiveData received the following:", Name);
Console.WriteLine(data);
ReceiveDataInvoked.Set();
if (data == "LastMessage")
{
ReceiveDataCompleted.Set();
}
}
public void ReceiveStream(Stream stream)
{
ReceiveStreamInvoked.Set();
int readResult;
byte[] buffer = new byte[1000];
do
{
try
{
readResult = stream.Read(buffer, 0, buffer.Length);
Console.WriteLine("[{0}] just read {1} bytes.{2}stream.Position = {3}", this.Name, readResult, Environment.NewLine, stream.Position);
}
catch (Exception ex)
{
Console.WriteLine("Caught exception when trying to read: {0}", ex);
throw;
}
}
while (readResult != 0);
stream.Close();
Console.WriteLine("[{0}] ReceiveStream invoked.", Name);
ReceiveStreamCompleted.Set();
}
public void ReceiveLog(List<string> log)
{
ServerLog = log;
LogReceived.Set();
}
}
}
step 4 : Generate a client to call the service.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace DuplexStreamingClient
{
class Program
{
static void Main(string[] args)
{
RunTests();
}
private static void RunTests()
{
string address = "net.tcp://localhost/IISHost/DuplexStreamService/DuplexService.svc";
NetTcpBinding binding = new NetTcpBinding();
ClientReceiver clientReceiver = new ClientReceiver();
DuplexChannelFactory<IDuplexService> channelFactory = new DuplexChannelFactory<IDuplexService>(new InstanceContext(clientReceiver), binding, address);
IDuplexService client = channelFactory.CreateChannel();
Console.WriteLine(client.DownloadData());
Console.WriteLine("Invoking StartPushingStream - get the server to push a stream to the client.");
client.StartPushingStream();
Console.WriteLine("Waiting on ReceiveStreamInvoked from the ClientReceiver.");
clientReceiver.ReceiveStreamInvoked.WaitOne();
clientReceiver.ReceiveStreamInvoked.Reset();
Console.WriteLine("Invoking StopPushingStream");
client.StopPushingStream();
Console.WriteLine("Waiting on ReceiveStreamCompleted from the ClientReceiver.");
clientReceiver.ReceiveStreamCompleted.WaitOne();
clientReceiver.ReceiveStreamCompleted.Reset();
Console.WriteLine("Getting results from server via callback.");
client.GetLog();
clientReceiver.LogReceived.WaitOne();
Console.WriteLine("----Following are the logs from the server:-----");
foreach (string serverLogItem in clientReceiver.ServerLog)
{
Console.WriteLine(serverLogItem);
}
Console.WriteLine("---------------- End server log. ---------------");
((IChannel)client).Close();
Console.WriteLine("Test passed.");
}
}
}
It's the basic usage for duplex transfer mode in WCF. Farther we will talk about some advanced Scenario for Duplex. Like Build a WebSocket with WCF and Broadcast Service with WCF.