如何:使用通道模型访问双工服务

本主题介绍如何创建可以访问 Windows Communication Foundation (WCF) 双工服务的 Silverlight 2.0 版本 客户端。

本文假定您已通过完成在如何:构建双工服务中描述的过程构建了 WCF 双工服务。本文中简要介绍的过程说明了如何构建 Silverlight 2.0 客户端,该客户端向双工服务发送订单,并由服务回调两次,第一次是在服务开始处理订单时,第二次是在订单完成时。

构建 Silverlight 双工客户端涉及大量异步调用,在调用过程中定义回调方法。这些异步调用使用标准结构。在以下过程中,为清楚起见省略了一些回调。在过程结尾提供了包含所有必要方法的完整源代码。

创建 Silverlight 客户端应用程序

  1. 在 Visual Studio 2008 中,通过完成下列步骤在 DuplexService 的当前解决方案中创建新的 Silverlight 客户端项目:

    1. 在**“解决方案资源管理器”(位于右上角)中包含该服务的同一解决方案中,右击当前解决方案(不是项目),依次选择“添加”“新建项目”**。
    2. 在**“添加新项目”对话框中,选择您的首选编程语言(C# 或 Visual Basic)的 Silverlight,然后选择“Silverlight 应用程序”**模板,将其命名为 DuplexClient。使用默认的位置。
    3. 单击**“确定”**。
  2. 在**“添加 Silverlight 应用程序”向导中,接受默认选择“将此 Silverlight 控件链接到现有网站”和其他默认选择,然后单击“确定”**。

构建双工客户端

  1. 在**“解决方案资源管理器”中右击 DuplexClient 项目,然后选择“添加引用…”。单击“添加引用”对话框中的 .NET 选项卡(如果它尚未选中)并定位到 System.ServiceModel.PollingDuplex.dll 程序集,选中它,然后单击“确定”**。使用相同的步骤添加对 System.ServiceModel.dllSystem.Runtime.Serialization.dll 程序集的引用。

    Cc645028.note(zh-cn,VS.95).gif
    请注意,Silverlight 2.0 SDK 附带有两个名为 System.ServiceModel.PollingDuplex.dll 的程序集。其中一个用于 WCF 双工服务,另一个用于 Silverlight 双工客户端。正确按照前面的步骤操作以到达正确位置,这一点很重要,以便您能够为双工客户端引用正确的程序集。

  2. 将下列 using 语句添加至 Page.xaml.cs 的顶部。

    using System.ServiceModel;
    using System.ServiceModel.Channels;
    
  3. 使用以下代码覆盖 Page.xaml.cs 上的 Page 方法。

    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);
        }
    }
    

    此代码实例化 PollingDuplexBindingElement,该元素用于在 Silverlight 客户端上创建可以与 WCF 双工服务通信的通道。配置了此绑定后,Silverlight 通道将定期在网络层上轮询服务,并检查服务要发送给客户端的任何新消息。InactivityTimeout 属性确定在客户端和服务之间没有任何消息交换的情况下,在客户端关闭其会话之前可以经过的时间间隔(以毫秒为单位)。请注意,此上下文中的“消息交换”指的是由服务或客户端上的方法发送的真实消息,而不是网络层上的轮询。

    于是,该绑定元素可用来异步打开通道工厂。此异步操作在调用 CompleteOpenFactory 方法后结束。OnOpenCompleteFactory 是标准回调,因此省略。

  4. 我们将 CompleteOpenFactory 方法定义如下。应将此代码粘贴到 DuplexClient 命名空间的 Page 方法后。

    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);
        }
    
    }
    

    此代码使用通道工厂创建 IDuplexSessionChannel 通道。请注意,所指定的终结点地址是以前构建的 WCF 双工服务地址。可以通过以下步骤获得该地址:右击 DuplexService 项目中的 Service1.svc 文件,选择**“在浏览器中查看”**,然后在浏览器窗口中观察地址。

    Cc645028.note(zh-cn,VS.95).gif
    必须将您的服务生成的正确地址粘贴到此处的代码中,以便客户端可以访问您的服务。

    将异步打开通道,并且此异步操作在调用 CompleteOpenChannel 方法时结束。OnOpenCompleteChannel 是标准回调,因此省略。

  5. CompleteOpenChannel 方法定义如下。

    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);
    }
    

    该方法首先做的事情是构建要发送给服务的 Message。请注意,在构建消息时必须使用 Soap11,因为双工服务只支持不含 WS-Addressing 的 SOAP 1.1 消息。而且,消息操作必须与在服务上定义的 IDuplexService 协定中的操作名称匹配。然后使用该通道来异步发送消息,并且异步操作在调用 CompleteOnSend 方法时结束。OnSendCompleteOnSend 是标准回调并已省略。

    将消息发送给服务后,ReceiveLoop 方法将启动接收循环,用于侦听由服务发回的消息。

  6. 我们将 ReceiveLoopCompleteReceive 方法定义如下。OnReceiveComplete 是标准回调并已省略。高级模式是 ReceiveLoop 方法引发一个事件,用于侦听来自服务的消息。收到消息后,将调用 CompleteReceive 回调。CompleteReceive 回调处理消息,然后可以结束接收循环,或者通过再次调用 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.
        }
    
    }
    

    注意对 EndReceive 的调用,此调用得到从服务接收的 Message。我们通过调用 GetBody 处理此消息并反序列化其正文。当我们收到表明订单已完成的消息时,退出接收循环并关闭通道。

    此处省略了 WriteText 方法和 Page.xaml 文件。它们的功能是将结果打印到 UI 控件上。请查看下面的代码摘要以检查此代码及其位置。OnCloseChannelCompleteCloseChannel 是标准回调并也已省略。

  7. Silverlight 双工客户端现已完成。使用本主题结尾处的代码添加省略的代码和 Page.xaml 文件后,运行示例时应当显示下列输出。

    Client says: Sent order of Widgets
    Service will call back with updates
    Service says: Processing Widgets order
    Service says: Widgets order complete
    

示例

下列代码示例汇总了完成上述过程后的 Page.xaml.cs 文件中应具备的内容。

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

    }
}

下面显示了完成上述过程后的 Page.xaml 文件中的代码。

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

向 Microsoft 发送对本主题的评论。

版权所有 © 2008 Microsoft Corporation。保留所有权利。