[This topic is pre-release documentation and is subject to change in future releases. Blank topics are included as placeholders.] This topic describes how to create a Silverlight version 2.0 client that can access a Windows Communication Foundation (WCF) duplex service.
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.0 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.
.gif) |
|---|
| Note that there are two assemblies named System.ServiceModel.PollingDuplex.dll that ship with the 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()
{
PollTimeout = TimeSpan.FromSeconds(10),
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 PollTimeout property determines the maximum allowed interval of time (in milliseconds) that the duplex service can hold a poll from the client before the poll times out. 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. The OnOpenCompleteFactory callback is standard and is omitted.
We define the CompleteOpenFactory method as follows. This code should be pasted into the DuplexClient namespace after the Page 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("http://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.
.gif) |
|---|
| 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. The OnOpenCompleteChannel callback is standard and is omitted.
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 BP 1.1 messages with no 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 the CompleteOnSend method is invoked. The OnSend and CompleteOnSend callbacks are standard and have been omitted.
After the message is sent to the service, the ReceiveLoop method initiates a receive loop, which listens for messages sent back by the service.
We define the ReceiveLoop and CompleteReceive methods as shown here. The OnReceiveComplete callback is standard and has been omitted. The high-level pattern is that the ReceiveLoop method raises an event to listen for messages from the service. After a message is received, the CompleteReceive callback is invoked. The CompleteReceive callback then processes the message and either ends the receive loop or continues it by again calling ReceiveLoop.
|
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.
}
}
|
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.
The WriteText method and the Page.xaml file have been omitted here. Their function is to print the results to a UI control. See the following code summary to examine this code and where it goes. The OnCloseChannel and CompleteCloseChannel are standard and have also been omitted.
The Silverlight duplex client is now complete. After the omitted code and the Page.xaml file are added using the code at the end of this topic, the following output should be displayed when running the sample.
|
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()
{
PollTimeout = TimeSpan.FromSeconds(10),
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("http://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="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock x:Name="reply" />
</Grid>
</UserControl>
|
Copyright © 2008 by Microsoft Corporation. All rights reserved.