为 Silverlight 客户端生成双工服务
本主题描述了如何创建可与 Silverlight 客户端进行通信的双工 Windows Communication Foundation (WCF) 服务。双工服务将保留到 Silverlight 客户端的回调通道,它允许该服务对此客户端进行调用。双工服务具有许多应用程序,例如,包括用于即时消息传递的聊天服务程序或用于向客户端发送通知的监视服务。本示例提供一项服务,该服务允许客户端按名称对指定数量的产品项进行排序。该服务将模拟订单处理过程,然后使用订单的状态回调客户端。
在双工消息传递中,服务和客户端都公开服务协定,这会指定服务和客户端分别向对方所公开的操作。在此示例中,我们使用下面一对服务协定。客户端将对服务调用 Order 方法,而服务将对客户端调用 Receive 方法。
[ServiceContract(Namespace="Silverlight", CallbackContract = typeof(IDuplexClient))]
public interface IDuplexService
{
[OperationContract]
void Order(string name, int quantity);
}
[ServiceContract]
public interface IDuplexClient
{
[OperationContract(IsOneWay = true)]
void Receive();
}
请注意,CallbackContract 属性用于指定回调客户端的服务协定。
另外请注意,对于作为回调协定一部分的每个客户端方法,IsOneWay 属性在 OperationContractAttribute 中也必须设置为 true。此属性表示这些操作不返回答复消息。
创建 WCF 双工服务以使用 Silverlight
-
打开 Visual Studio 2010。
-
从"文件"菜单中依次选择"新建"、"项目",从您要在其中进行编码的语言(C# 或 Visual Basic)组中左侧列出的"项目类型"中指定"WCF"。然后,从右侧的"模板"中选择"WCF 服务应用程序",在下面的"名称"框中将其命名为 DuplexService,再单击"确定"。
-
使用针对 DuplexService 命名空间的下列代码替换 IService1.cs 文件的默认内容。此代码使用 interfaces 来定义服务和客户端所用的协定。
//Interface contracts in the IService1.cs file.
namespace DuplexService
{
[ServiceContract(Namespace="Silverlight", CallbackContract = typeof(IDuplexClient))]
public interface IDuplexService
{
[OperationContract]
void Order(string name, int quantity);
}
[ServiceContract]
public interface IDuplexClient
{
[OperationContract(IsOneWay = true)]
void Receive(Order order);
}
}
请注意,此代码就是在讨论 WCF 服务与 Silverlight 客户端之间的双工通信的本质以及它们为定义所用的消息模式而公开的服务协定时,在本主题的简介中显示的代码。
4.编辑文件 Service1.svc.cs 的内容以定义实施 IDuplexService
协定的
OrderService
类。首先,移除 DuplexService
命名空间声明内的所有类。然后定义
OrderService
类,如下面的代码所示。
public class OrderService : IDuplexService
{
IDuplexClient client;
string orderName;
int orderQuantity;
public void Order(string name, int quantity)
{
// Grab the client callback channel.
client = OperationContext.Current.GetCallbackChannel<IDuplexClient>();
orderName = name;
orderQuantity = quantity;
// Pretend service is processing and will call client back in 5 seconds.
using (Timer timer = new Timer(new TimerCallback(CallClient), null, 5000, 5000))
{
Thread.Sleep(11000);
}
}
}
Order
操作模拟执行从在线商店订购商品的过程。接收到订单后,通常会执行服务范围之外的一系列后续操作(例如,检查商品的供应情况或者核对用户信用卡的信息)。完成这些操作后,该服务需要回调客户端(即,将信息"推"到客户端),以通知它订单已成功完成。为模拟此情况,我们使用一个
Timer,它将在两个
5 秒的间隔后调用两次该服务。
请注意,使用 OperationContext.GetCallbackChannel(T)
方法来获得类型为 IDuplexClient
的回调通道,这可用于在实现和运行 Silverlight 客户端后联系该客户端。
5. 在 DuplexService
命名空间中,添加一个 Order
类和一个辅助的
OrderStatus
枚举,以表示该服务可以将订单相关信息发送回客户端这一信息。
public class Order
{
public OrderStatus Status { get; set; }
public List<string> Payload { get; set; }
}
public enum OrderStatus
{
Processing,
Completed
}
6.在 OrderService
类中,添加下列内容。
bool processed = false;
private void CallClient(object o)
{
Order order = new Order();
order.Payload = new List<string>();
// Process order.
if (processed)
{
// Pretend service is fulfilling the order.
while (orderQuantity-- > 0)
{
order.Payload.Add(orderName + orderQuantity);
}
order.Status = OrderStatus.Completed;
}
else
{
// Pretend service is processing payment.
order.Status = OrderStatus.Processing;
processed = true;
}
// Call client back.
client.Receive(order);
}
调用该方法两次以模拟服务上的订单处理活动。第一次调用时,它将报告正在处理订单,第二次调用时,它将报告已完成订单并填充订单的
Payload
属性。
7.若要将 DuplexService.OrderService 指定为 Service1.svc 文件中的服务,请右击"解决方案资源管理器",选择"查看标记",并粘贴以下服务指令以覆盖默认指令。
// Directive contained in the Service1.svc file.
<%@ServiceHost language="c#" Debug="true" Service="DuplexService.OrderService" CodeBehind="Service1.svc.cs "%>
现在已经开发了服务的服务协定,接下来我们必须在 Silverlight 4 可识别的两种双工传输方法之间进行选择。
使用 PollingDuplexHttpBinding
1.在"解决方案资源管理器"中右击"DuplexService"项目,然后选择"添加引用"。单击"浏览"选项卡,并导航到 System.ServiceModel.PollingDuplex.dll 程序集(位于 %ProgramFiles%\Microsoft SDKs\Silverlight\v4.0\Libraries\Server\ 目录中),选择该程序集并单击"确定"。
请注意,Silverlight 版本 4 SDK 附带两个名为 System.ServiceModel.PollingDuplex.dll 的程序集。WCF 双工服务使用其中一个程序集(位于 %ProgramFiles%\Microsoft SDKs\Silverlight\v4.0\Libraries\Server\ 目录下),也就是您刚才添加了对它的引用的程序集。另一个程序集(位于 %ProgramFiles%\Microsoft SDKs\Silverlight\v4.0\Libraries\Client\ 目录下)在 Silverlight 双工客户端中使用。务必按前面的步骤正确操作以到达正确位置,这样您才能为双工服务引用正确的程序集,并且随后为双工客户端应用程序引用正确的程序集。
2.在"解决方案资源管理器"中右击 Web.config 文件,然后选择"打开"以编辑此配置。首先,通过在 <system.serviceModel> 节中添加以下代码,注册 Silverlight 4 SDK 中的 pollingDuplexHttpBinding
<!-- Register the binding extension from the SDK. -->
<extensions>
<bindingExtensions>
<add name=
"pollingDuplexHttpBinding"
type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
<!-- Create the polling duplex binding. -->
<pollingDuplexHttpBinding>
<binding name="multipleMessagesPerPollPollingDuplexHttpBinding"
duplexMode="MultipleMessagesPerPoll"
maxOutputDelay="00:00:07"/>
</pollingDuplexHttpBinding>
4.然后,在 <services> 节中指定服务终结点。
<services>
<service name="DuplexService.OrderService"
behaviorConfiguration="DuplexService.OrderServiceBehavior">
<!-- Service Endpoints -->
<endpoint
address=""
binding="pollingDuplexHttpBinding"
bindingConfiguration="multipleMessagesPerPollPollingDuplexHttpBinding"
contract="DuplexService.IDuplexService">
</endpoint>
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
5.现在,Web.config 文件中的 <system.serviceModel> 节应包含以下配置元素。
<!-- Register the binding extension from the SDK. -->
<extensions>
<bindingExtensions>
<add name="pollingDuplexHttpBinding"
type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,
System.ServiceModel.PollingDuplex,
Version=4.0.0.0,
Culture=neutral,
PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
<!-- Create the polling duplex binding. -->
<bindings>
<pollingDuplexHttpBinding>
<binding name="multipleMessagesPerPollPollingDuplexHttpBinding" duplexMode="MultipleMessagesPerPoll" maxOutputDelay="00:00:07"/>
</pollingDuplexHttpBinding>
</bindings>
<services>
<service name="DuplexService.OrderService">
<!-- Service Endpoints -->
<endpoint
address=""
binding="pollingDuplexHttpBinding"
bindingConfiguration="multipleMessagesPerPollPollingDuplexHttpBinding"
contract="DuplexService.IDuplexService">
</endpoint>
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Threading;
namespace DuplexService
{
public class OrderService : IDuplexService
{
IDuplexClient client;
string orderName;
int orderQuantity;
public void Order(string name, int quantity)
{
// Grab the client callback channel.
client = OperationContext.Current.GetCallbackChannel<IDuplexClient>();
orderName = name;
orderQuantity = quantity;
// Pretend the service is processing and will call the client
// back in 5 seconds.
using (Timer timer = new Timer(new TimerCallback(CallClient), null, 5000, 5000))
{
Thread.Sleep(11000);
}
}
bool processed = false;
private void CallClient(object o)
{
Order order = new Order();
order.Payload = new List<string>();
// Process the order.
if (processed)
{
// Pretend the service is fulfilling the order.
while (orderQuantity-- > 0)
{
order.Payload.Add(orderName + orderQuantity);
}
order.Status = OrderStatus.Completed;
}
else
{
// Pretend the service is processing the payment.
order.Status = OrderStatus.Processing;
processed = true;
}
// Call the client back.
client.Receive(order);
}
}
public class Order
{
public OrderStatus Status { get; set; }
public List<string> Payload { get; set; }
}
public enum OrderStatus
{
Processing,
Completed
}
}