How to: Access a Duplex Service with the Channel Model
This topic describes how to create a Silverlight version 2 client that can access a Windows Communication Foundation (WCF) duplex service that is configured with a PollingDuplexHttpBinding.
It assumes that you have already built a WCF duplex service by completing the procedure described in How to: Build a Duplex Service. The procedure outlined here describes how to construct a Silverlight 2 client that sends an order to a duplex service and that is called back by that service twice, first when the service starts to process the order, and second when the order is completed.
Building a Silverlight duplex client involves a significant number of asynchronous calls, where callback methods are defined. The structure used for these asynchronous calls is standard. In the following procedure, some callbacks are omitted for clarity. The full source code, which contains all necessary methods, is available at the end of the procedure.
Creating the Silverlight client application
Create a new Silverlight client project in the current solution for the DuplexService in Visual Studio 2008 by completing the following steps:
In Solution Explorer (on the upper-right) in the same solution that contains the service, right-click the current solution (not the project), select Add, and then New Project.
In the Add New Project dialog box, select Silverlight in your preferred programming language (C# or Visual Basic), then select the Silverlight Application template, and name it DuplexClient. Use the default Location.
Click OK.
In the Add Silverlight Application wizard, accept the default selection of Link this Silverlight control into an existing Web site and other defaults and then click OK.
Building a duplex client
Right-click the DuplexClient project in Solution Explorer and select Add Reference…. Click the .NET tab (if it is not already selected) in the Add Reference dialog box and navigate to the System.ServiceModel.PollingDuplex.dll assembly; select it, and click OK. Using the same procedure, add references to the System.ServiceModel.dll and System.Runtime.Serialization.dll assemblies.
Note that there are two assemblies named System.ServiceModel.PollingDuplex.dll that ship with the Silverlight 2 SDK. One of them is used in WCF duplex services, and the other one is used in Silverlight duplex clients. It is important to follow the preceding steps correctly to the right location, so that you reference the correct assembly for the duplex client. Add the following using statements to the top of Page.xaml.cs.
using System.ServiceModel; using System.ServiceModel.Channels;
Overwrite the
Page
method on the Page.xaml.cs with the following code.SynchronizationContext uiThread; public Page() { InitializeComponent(); // Grab a reference to the UI thread. uiThread = SynchronizationContext.Current; // Instantiate the binding and set the time-outs. PollingDuplexHttpBinding binding = new PollingDuplexHttpBinding() { InactivityTimeout = TimeSpan.FromMinutes(1) }; // Instantiate and open channel factory from binding. IChannelFactory<IDuplexSessionChannel> factory = binding.BuildChannelFactory<IDuplexSessionChannel>(new BindingParameterCollection()); IAsyncResult factoryOpenResult = factory.BeginOpen(new AsyncCallback(OnOpenCompleteFactory), factory); if (factoryOpenResult.CompletedSynchronously) { CompleteOpenFactory(factoryOpenResult); } }
This code instantiates a PollingDuplexBindingElement, which is used to create a channel on the Silverlight client that can communicate with a WCFduplex service. When configured with this binding, the Silverlight channel periodically polls the service on the network layer, and checks for any new messages that the service wants to send to the client. The
InactivityTimeout
property determines the interval of time (in milliseconds) that can elapse without any message exchange between the client and the service before the client closes its session. Note that “message exchange” in this context refers to real messages sent by methods on the service or client, and does not refer to polls on the network layer.The binding element can then be used to open a channel factory asynchronously. This asynchronous operation ends when the
CompleteOpenFactory
method is invoked.Define the
OnOpenCompleteFactory
callback method as follows.void OnOpenCompleteFactory(IAsyncResult result) { if (result.CompletedSynchronously) return; else CompleteOpenFactory(result); }
Define the
CompleteOpenFactory
method as follows. This code should be pasted into the DuplexClient namespace after thePage
method.void CompleteOpenFactory(IAsyncResult result) { IChannelFactory<IDuplexSessionChannel> factory = (IChannelFactory<IDuplexSessionChannel>)result.AsyncState; factory.EndOpen(result); // Factory is now open. Create and open the channel from the channel factory. IDuplexSessionChannel channel = factory.CreateChannel(new EndpointAddress("https://localhost:19021/Service1.svc")); IAsyncResult channelOpenResult = channel.BeginOpen(new AsyncCallback(OnOpenCompleteChannel), channel); if (channelOpenResult.CompletedSynchronously) { CompleteOpenChannel(channelOpenResult); } }
This code uses the channel factory to create a IDuplexSessionChannel channel. Note that the endpoint address specified is the address of the WCF duplex service built earlier. You can obtain the address by right-clicking the Service1.svc file in the DuplexService project, selecting View in Browser, and then observing the address in the browser window.
Note
You will have to paste the correct address generated by your service into the code here for your client to access your service.
The channel is opened asynchronously and the asynchronous operation ends when the
CompleteOpenChannel
method is invoked.Define the
OnOpenCompleteChannel
callback method as follows.void OnOpenCompleteChannel(IAsyncResult result) { if (result.CompletedSynchronously) return; else CompleteOpenChannel(result); }
Define the
CompleteOpenChannel
method as follows.string order = "Widgets"; void CompleteOpenChannel(IAsyncResult result) { IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState; channel.EndOpen(result); // The channel is now open. Send a message. Message message = Message.CreateMessage(channel.GetProperty<MessageVersion>(), "Silverlight/IDuplexService/Order", order); IAsyncResult resultChannel = channel.BeginSend(message, new AsyncCallback(OnSend), channel); if (resultChannel.CompletedSynchronously) { CompleteOnSend(resultChannel); } // Also start the receive loop to listen for callbacks from the service. ReceiveLoop(channel); }
The first thing the method does is to construct a Message to send to the service. Note that in constructing the message, Soap11 must be used because duplex services support only SOAP 1.1 messages with no WS-Addressing. Also, the message action must match the name of the operation in the
IDuplexService
contract defined on the service. Then the channel is used to send the message asynchronously, and the asynchronous operation ends when theCompleteOnSend
method is invoked.After the message is sent to the service, the
ReceiveLoop
method initiates a receive loop, which listens for messages sent back by the service.Define the
OnSend
callback method as follows.void OnSend(IAsyncResult result) { if (result.CompletedSynchronously) return; else CompleteOnSend(result); }
Define the
CompleteOnSend
callback method as follows.void CompleteOnSend(IAsyncResult result) { IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState; channel.EndSend(result); // The message is now sent. Notify the user. uiThread.Post(WriteText, "Client says: Sent order of " + order + Environment.NewLine); uiThread.Post(WriteText, "Service will call back with updates" + Environment.NewLine); }
Define the
ReceiveLoop
,CompleteReceive
, andOnReceiveComplete
methods as shown here. The high-level pattern is that theReceiveLoop
method raises an event to listen for messages from the service. After a message is received, theCompleteReceive
callback is invoked. TheCompleteReceive
callback then processes the message and either ends the receive loop or continues it by again callingReceiveLoop
.void ReceiveLoop(IDuplexSessionChannel channel) { // Start listening for callbacks. IAsyncResult result = channel.BeginReceive(new AsyncCallback(OnReceiveComplete), channel); if (result.CompletedSynchronously) CompleteReceive(result); } void CompleteReceive(IAsyncResult result) { // A callback was received. IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState; try { Message receivedMessage = channel.EndReceive(result); if (receivedMessage == null) { // Server closed its output session, can close client channel, // or continue sending messages to start a new session. } else { // Show the service response in the UI. string text = receivedMessage.GetBody<string>(); uiThread.Post(WriteText, "Service says: " + text + Environment.NewLine); // Check whether the order is complete. if (text == order + " order complete") { // If the order is complete, close the client channel. IAsyncResult resultFactory = channel.BeginClose(new AsyncCallback(OnCloseChannel), channel); if (resultFactory.CompletedSynchronously) { CompleteCloseChannel(result); } } else { // If the order is not complete, continue listening. ReceiveLoop(channel); } } } catch (CommunicationObjectFaultedException) { // The channel inactivity time-out was reached. } } void OnReceiveComplete(IAsyncResult result) { if (result.CompletedSynchronously) return; else CompleteReceive(result); }
Note the call to EndReceive, which yields the Message received from the service. We process the message and deserialize its body, by calling GetBody. After we receive the message stating that the order has been completed, we exit the receive loop and close the channel.
Define the
WriteText
,OnCloseChannel
, andCompleteCloseChannel
methods as follows.void WriteText(object text) { reply.Text += (string)text; } void OnCloseChannel(IAsyncResult result) { if (result.CompletedSynchronously) return; else CompleteCloseChannel(result); } void CompleteCloseChannel(IAsyncResult result) { IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState; channel.EndClose(result); // The client channel is now closed. }
Modify the UserControl element in the Page.xaml as as follows.
<UserControl x:Class="DuplexClient.Page" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300"> <Grid x:Name="LayoutRoot" Background="White"> <TextBlock x:Name="reply" /> </Grid> </UserControl>
The Silverlight duplex client is now complete. To run the sample right click on
DuplexClientTestPage.html
in the Solution Explorer and select View in Browser. You should see the following output.Client says: Sent order of Widgets Service will call back with updates Service says: Processing Widgets order Service says: Widgets order complete
Example
The following code sample summarizes what should be in the Page.xaml.cs file after completing the preceding procedure.
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
using System.Windows.Controls;
namespace DuplexClient
{
public partial class Page : UserControl
{
SynchronizationContext uiThread;
public Page()
{
InitializeComponent();
// Grab a reference to the UI thread.
uiThread = SynchronizationContext.Current;
// Instantiate the binding and set the time-outs.
PollingDuplexHttpBinding binding = new PollingDuplexHttpBinding()
{
InactivityTimeout = TimeSpan.FromMinutes(1)
};
// Instantiate and open channel factory from binding.
IChannelFactory<IDuplexSessionChannel> factory =
binding.BuildChannelFactory<IDuplexSessionChannel>(new BindingParameterCollection());
IAsyncResult factoryOpenResult =
factory.BeginOpen(new AsyncCallback(OnOpenCompleteFactory), factory);
if (factoryOpenResult.CompletedSynchronously)
{
CompleteOpenFactory(factoryOpenResult);
}
}
void OnOpenCompleteFactory(IAsyncResult result)
{
if (result.CompletedSynchronously)
return;
else
CompleteOpenFactory(result);
}
void CompleteOpenFactory(IAsyncResult result)
{
IChannelFactory<IDuplexSessionChannel> factory =
(IChannelFactory<IDuplexSessionChannel>)result.AsyncState;
factory.EndOpen(result);
// The factory is now open. Create and open a channel from the channel factory.
IDuplexSessionChannel channel =
factory.CreateChannel(new EndpointAddress("https://localhost:19021/Service1.svc"));
IAsyncResult channelOpenResult =
channel.BeginOpen(new AsyncCallback(OnOpenCompleteChannel), channel);
if (channelOpenResult.CompletedSynchronously)
{
CompleteOpenChannel(channelOpenResult);
}
}
void OnOpenCompleteChannel(IAsyncResult result)
{
if (result.CompletedSynchronously)
return;
else
CompleteOpenChannel(result);
}
string order = "Widgets";
void CompleteOpenChannel(IAsyncResult result)
{
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
channel.EndOpen(result);
// The channel is now open. Send a message.
Message message =
Message.CreateMessage(channel.GetProperty<MessageVersion>(),
"Silverlight/IDuplexService/Order", order);
IAsyncResult resultChannel =
channel.BeginSend(message, new AsyncCallback(OnSend), channel);
if (resultChannel.CompletedSynchronously)
{
CompleteOnSend(resultChannel);
}
// Also start the receive loop to listen for callbacks from the service.
ReceiveLoop(channel);
}
void OnSend(IAsyncResult result)
{
if (result.CompletedSynchronously)
return;
else
CompleteOnSend(result);
}
void CompleteOnSend(IAsyncResult result)
{
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
channel.EndSend(result);
// The message is now sent. Notify the user.
uiThread.Post(WriteText, "Client says: Sent order of " + order + Environment.NewLine);
uiThread.Post(WriteText, "Service will call back with updates" + Environment.NewLine);
}
void ReceiveLoop(IDuplexSessionChannel channel)
{
// Start listening for callbacks.
IAsyncResult result = channel.BeginReceive(new AsyncCallback(OnReceiveComplete), channel);
if (result.CompletedSynchronously)
CompleteReceive(result);
}
void OnReceiveComplete(IAsyncResult result)
{
if (result.CompletedSynchronously)
return;
else
CompleteReceive(result);
}
void CompleteReceive(IAsyncResult result)
{
// A callback was received.
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
try
{
Message receivedMessage = channel.EndReceive(result);
if (receivedMessage == null)
{
// Server closed its output session, can close client channel,
// or continue sending messages to start a new session.
}
else
{
// Show the service response in the UI.
string text = receivedMessage.GetBody<string>();
uiThread.Post(WriteText, "Service says: " + text + Environment.NewLine);
// Check whether the order is complete.
if (text == order + " order complete")
{
// If the order is complete, close the client channel.
IAsyncResult resultFactory =
channel.BeginClose(new AsyncCallback(OnCloseChannel), channel);
if (resultFactory.CompletedSynchronously)
{
CompleteCloseChannel(result);
}
}
else
{
// If the order is not complete, continue listening.
ReceiveLoop(channel);
}
}
}
catch (CommunicationObjectFaultedException)
{
// The channel inactivity time-out was reached.
}
}
void WriteText(object text)
{
reply.Text += (string)text;
}
void OnCloseChannel(IAsyncResult result)
{
if (result.CompletedSynchronously)
return;
else
CompleteCloseChannel(result);
}
void CompleteCloseChannel(IAsyncResult result)
{
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
channel.EndClose(result);
// The client channel is now closed.
}
}
}
The following shows the code in the Page.xaml file after completing the preceding procedure.
<UserControl x:Class="DuplexClient.Page"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock x:Name="reply" />
</Grid>
</UserControl>
Change Date | History | Reason |
---|---|---|
11/7/2008 |
Added the steps that were called out but not shown explicitly. The procedure is now completely specified. Also noted at the start of the topic that Silverlight duplex used in this procedure is a polling duplex that uses the PollingDuplexHttpBinding and not the standard WCF duplex that uses the WSDualHttpBinding. |
Responding to customer feedback that the procedure was difficult to follow. |