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

  1. Create a new Silverlight client project in the current solution for the DuplexService in Visual Studio 2008 by completing the following steps:

    1. 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.

    2. 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.

    3. Click OK.

  2. 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

  1. 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.

    Cc645028.note(en-us,VS.95).gif
    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.

  2. Add the following using statements to the top of Page.xaml.cs.

    using System.ServiceModel;
    using System.ServiceModel.Channels;
    
  3. 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.

  4. Define the OnOpenCompleteFactory callback method as follows.

    void OnOpenCompleteFactory(IAsyncResult result)
    {
       if (result.CompletedSynchronously)
          return;
       else
          CompleteOpenFactory(result);
    }
    
  5. 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("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.

  6. Define the OnOpenCompleteChannel callback method as follows.

    void OnOpenCompleteChannel(IAsyncResult result)
    {
       if (result.CompletedSynchronously)
          return;
       else
          CompleteOpenChannel(result);
    }
    
  7. 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 the CompleteOnSend 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.

  8. Define the OnSend callback method as follows.

    void OnSend(IAsyncResult result)
    {
       if (result.CompletedSynchronously)
          return;
       else
          CompleteOnSend(result);
    }
    
  9. 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);
    }
    
  10. Define the ReceiveLoop, CompleteReceive, and OnReceiveComplete methods as shown here. 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.
        }
    }
    
    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.

  11. Define the WriteText, OnCloseChannel, and CompleteCloseChannel 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.
    }
    
  12. 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>
    
  13. 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.

Send comments about this topic to Microsoft.