Wednesday, 13 March 2013

WCF: Duplex Contracts

WCF supports three types of message exchange patterns:

1. Request-Reply
This is the default mode. A point to note is that even if the return type of a service operation is “void” a response would still be sent back to the client.

2. One-way
In one-way operations, the service after queuing up the call doesn’t send anything back to the client. Few details:
- we need to apply “IsOneWay=true” on the operation we want to behave like a one-way
- method type return must be void
- for any exceptions in the service operation, client doesn’t receive any feedback (faults are not returned back).
- the client after making the call returns back immediately to continue execution even if the service operation is still in progress.

3. Duplex
Duplex allows two-way communication. The client makes a call to the service, and then later the service can make a separate call to the client (this call is different from the reply sent back to client for the first call). The call the service makes to the client has its own request-response flow. In Duplex communication, we need to have two contracts, one implemented by the service (incoming contract) and one by the client (callback contract).

Classic examples of duplex communication are chat applications, stock quote or game score notifications. Let’s try a stock quotes notification service.

Not all channels support duplex communication. The supported ones are: NetTcpBinding, NetNamedPipeBinding. and on HTTP we need to use the special wsDualHttpBinding (the standard http bindings - basicHttpBinding and wsHttpBinding are not bi-directional)

Demo - Stock Quote Service Application: Assume we are a service provider of stock quotes. We want to expose a service which would allow clients first to register with a stock symbol to receive stock quotes, and as long as client doesn’t disconnect or explicitly unregisters we will keep sending back messages with stock quotes.

Contracts:

We need two contracts in duplex:
- IRequestStockQuotes: incoming contract implemented at the service end
- IQuoteNotification: callback contract implemented by the client

namespace StockQuotes.Common
{
[ServiceContract(CallbackContract=typeof(IQuoteNotification))]
public interface IRequestStockQuotes
{
[OperationContract]
void RequestNotifications(string symbol);
}

[ServiceContract]
public interface IQuoteNotification
{
[OperationContract]
void SendQuote(StockQuote quote);
}
}

Service :

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class StockService : IRequestStockQuotes
{
public void RequestNotifications(string symbol)
{
Console.WriteLine("Got request for symbol:" + symbol);
IQuoteNotification client = OperationContext.Current.GetCallbackChannel<IQuoteNotification>();
StockQuote quote = QuoteEngine.GetQuote(symbol); //from a real-time quote server
client.SendQuote(quote);
}
}

The concurrency mode of the service needs to be ConcurrencyMode.Multiple or Reentrant, the default mode ConcurrencyMode.Single would create a deadlock situation. An alternative could be mark the callback operations “One-Way”.. In the service method we fetch the client callback from the OperationContext.Current.GetCallbackChannel<T> and call the method “SendQuote” to call the client.

Service Host:

ServiceHost host = null;
try
{
host = new ServiceHost(typeof(StockQuotesService.StockService));
host.Open();
foreach (ServiceEndpoint ep in host.Description.Endpoints)
{
Console.WriteLine(ep.Address.Uri.ToString());
}
Console.WriteLine("Stock service now running. \n<ENTER> to close.");
Console.ReadLine();
host.Close();
}
catch (CommunicationException)
{
if (host != null)
{
host.Abort();
}
}
catch (TimeoutException)
{
if (host != null)
{
host.Abort();
}
}
catch (Exception)
{
if (host != null)
{
host.Abort();
}
}

Client:

First we implement the callback contract -

namespace Client
{
public class QuoteNotification : IRequestStockQuotesCallback
{
public void SendQuote(StockQuote quote)
{
Console.WriteLine("Received quote (callback) from service: Symbol {0}, Quote {1}", quote.Symbol, quote.Quote);
}
}
}

An important point to note here is that , I used ‘Add Service Reference’ from Visual Studio and that’s why the name of callback contract was changed from IQuoteNotification to IRequestStockQuotesCallback. I think this was done by the code-generation utility to match the service interface name “IRequestStockQuotes”.

The calls:

static void Main(string[] args)
{
InstanceContext ctx = new InstanceContext(new QuoteNotification());
RequestStockQuotesClient client = null;

try
{
client = new RequestStockQuotesClient(ctx);
client.Open();
Console.WriteLine("ENTER to call service:");
Console.ReadLine();
client.RequestNotifications("MSFT");
client.RequestNotifications("INTL");
client.RequestNotifications("RIM");
client.RequestNotifications("NSDQ");
client.RequestNotifications("A");
Console.ReadLine();
client.Close();
}
catch (CommunicationException)
{
if (client != null)
{
client.Abort();
}
}
catch (TimeoutException)
{
if (client != null)
{
client.Abort();
}
}
catch (Exception)
{
if (client != null)
{
client.Abort();
}
}
}

Output:

Service console:


image
Client console:

image 
Further improvements:

What we did was our first implementation, there is a scope of improvement here. Right now the client calls the “RequestNotifications” service method and immediately the service sends back the quote. To get the next quote the client will again have to call the service, which is not a good implementation. Ideally the client should register to receive the quote stream and then whenever service gets a new quote it should send it to the client. This would create the live-streaming environment. We will try to accomplish that in next post.

No comments:

Post a Comment

Note: only a member of this blog may post a comment.