WCF 4 Cookbook 系列(二) 终结点,绑定和行为 [上]
在这篇文章中,将会包括:
- 配置默认终结点
- 在MSMQ上设立双向通信
- 通过双重绑定建立发布-订阅服务
- 创建一个多终结点服务
- 实现POX HTTP 服务
- 定义没有时间戳头的CustomBinding
- 在未知的SoapHeader禁用mustUnderstand验证
- 在多个终结点之间分享一个物理地址
简介
WCF通过终结点暴露服务,这样为客户端使用一个给定的WCF服务功能提供基本的访问点。服务终结点由ABC和一组行为组成。什么是ABC?A代表地址Address,它告诉服务的消费者“服务在哪”,B代表绑定Binding,它描述了“怎么和服务交流”,而C代表契约Contract,它展示了“服务提供了什么方法”。
WCF提供了大量的内置绑定(比如BasicHttpBinding, NetTcpBinding, NetMsmqBinding等等),它们可以帮助开发者在不同的传输协议上寄宿服务终结点。在WCF中行为同样起着重要的作用。通过使用行为,我们可以在WCF的服务或者终结点级别获得更多的操作。
本文提供8个部分有关使用内置的绑定和行为创建各种服务终结点,展示了在常规的WCF服务中一些有用的开发场景。我们将从WCF4.0的默认终结点特性将其。然后,使用两个内置的绑定(NetMsmqBinding 和 WSDualHttpBinding)来演示怎么创建一个双向通信,并通过系统绑定创建一个发布-订阅模式的服务。第4部分说明WCF如何在异构传输和消息设置上分层来允许一个单一的服务公开多个终结点。第5部分演示如何创建能和应用通信的POX风格的WCF服务,它只支持无格式的基于XML的消息通信。最后3部分带来在服务终结点自定义中一些常用的和复杂的情景。第6部分说明怎么禁用重放检测(replay detection 注:没有理解什么意思)和在一个常规WCF的SOAP消息中移除时间戳头,第7部分演示如何使WCF服务端/客户端兼容匿名的SoapHeader,最后一节提供一个使多个WCF终结点共享一个单一传输地址的示例。
配置默认终结点
在使用WCF编程的时候,我们常需要创建为测试我们的契约创建一些简单的WCF服务。这些服务通常使用非常简单和标准的终结点和绑定定义。然而,每次我们需要建立这样一个服务,就不得不一次又一次的定义相同的终结点和绑定的设置,这真的增加的太多的重复工作。幸运的是,WCF 4.0引入了默认终结点特性,帮助我们摆脱了重复地定义相同的终结点/绑定设置。
怎么做…
使用一个默认终结点的步骤是非常直截了当:
- 在Visual Studio 2010中创建一个新的使用.NET Framework 4.0的控制台应用程序项目
- 在服务端项目中添加服务契约并实现我们在服务端项目中需要的类型。我们可以在这里使用任何有效的服务契约和它相应的实现类。比如,下面的IHelloService服务包含一个单一的SayHello操作:
[ServiceContract] public interface IHelloService { [OperationContract] string SayHello(string user); }
- 在app.config文件中为服务类型添加服务配置条目。在这里我们不需要定义任何终结点和绑定配置。下面的截图展示了必要的服务配置元素:
- 启动一个ServiceHost ,并指定我们想要运行的服务类型(见下面的代码):
using (ServiceHost host = new ServiceHost(typeof(HelloService))) { host.Open(); Console.ReadLine(); }
原理…
在前文定义的服务和寄宿代码中,我们没有添加任何终结点和绑定配置。这个魔术的幕后是默认终结点特性。当我们启动一个WCF 4.0服务宿主,如果运行时没有发现任何终结点定义(通过app.config或者代码),它会自动为每一个为服务类实现了的服务契约创建一个默认的终结点。这个默认终结点将基于它的终结点地址(URL结构)选择合适的绑定,这个过程通过查找在系统配置库中的提前定义的一个协议映射列表来完成(在.NET 4 Machine.config.comments 文件中)。下面的截图展示了协议映射列表:
对于我们前文的示例,自从终结点地址使用HTTP结构(源于基地址baseAddress),运行时将会根据协议映射列表选择BasicHttpBinding。
通过使用DumpEndpoint方法,我们可以确定使用BasicHttpBinding的默认终结点在前面的案例中已经被正确设置(见下图)。
下面的截图展示了在示例服务中自动配置的默认终结点。
更多…
除了默认终结点,WCF 4.0也提供了默认绑定特性,用来节省需要为多个终结点定义相同绑定设置的开发人员的时间。例如,我们定义如下图那样的匿名绑定配置,没有明确的名称。任何使用BasicHttpBinding的终结点会采用这种匿名绑定配置的设置。
在MSMQ上设立双向通信
很长一段时间,MSMQ在微软平台上被认为是基于消息通信的主要的平台。微软.NET framework 也为开发人员开发基于MSMQ的分布式应用程序提供了可托管的编程接口。然而,对于开发者而已,通过原始的或者封装了MSMQ的编程接口的方式来创建一个完整的分布式服务仍然是非常复杂和费时的。作为Windows上新的统一通信开发平台,WCF对比基于MSMQ组件,为开发分布式服务提供了更多的便捷方式。
准备工作
如果不熟悉Microsoft Message Queuing (MSMQ),你可以从以下站点获得有用的信息:http://msdn.microsoft.com/en-us/library/ms711472(VS.85).aspx
同样,MSDN技术资源库提供了.NET Framework的详细参考。Messaging命名空间包含了原始的MSMQ编程接口(访问http://msdn.microsoft.com/en-us/library/system.messaging.aspx)。
怎么做…
使WCF客户端和服务端平台在MSMQ上实现双向通行,我们需要设置两个基于MSMQ的终结点——一个用在服务端接受客户端的请求,另一个用在客户端获取相应。
- 定义将在基于MSMQ服务中使用的服务契约。下面的代码片段展示了ServiceContract 的示例(一个用于服务端,另一个用于客户端):
[ServiceContract] public interface INotificationReceiver { [OperationContract(IsOneWay = true)] void Notify(long id, string msg, DateTime time); } [ServiceContract] public interface INotificationSender { [OperationContract(IsOneWay = true)] void Ack(long id); }
基于MSMQ的所有服务操作都应该被标记为one-way 格式。
-
在客户端和服务端的机器上创建MSMQ队列。有两种方法来创建队列:一种是使用MMC管理单元(MMC snap-in),有很多方法启动这个管理单元,但是最简单的方式是在管理工具中打开Windows计算机管理程序,展开左侧的服务和应用程序部分的树视图,选择消息队列节点。这是核实特定机器上已经安装了MSMQ的非常好的方式。下面的截图展示了标准的MSMQ管理单元的UI。
另外一种方式是创建可编程的队列,正如下面代码演示的。在这里的示例服务中,我们将在代码中创建MSMQ队列:
private static void Init() { // Ensure the message queue exists string qName = ConfigurationManager.AppSettings[“ReceiverQueue”]; if (MessageQueue.Exists(qName)) MessageQueue.Delete(qName); MessageQueue q = MessageQueue.Create(qName, false); }
-
在消息队列被创建之后,我们就可以配置服务端和客户端的终结点,并映射它们到底层MSMQ队列。对于接收端,服务终结点应该使用NetMsmqBinding并设置地址格式为net.msmq://,下面的截图展示了一个简单的服务终结点配置:
在终结点地址中,private表明MSMQ队列是一个私有队列,NotificationReceiver是队列的名称。
在终结点被正确配置之后,我们能够想其他普通的WCF服务一样寄宿和消费基于MSMQ的服务。
原理…
由于MSMQ物理上只支持one-way消息传递,我们需要在客户端和服务端机器上都寄宿一个基于MSMQ的WCF服务以便建立two-way通信。
另外,WCF NetMSMQBinding是一个WCF固有的绑定,它完全隐藏了底层MSMQ处理细节;开发者只需要集中精力在服务契约和服务终结点的配置上,代替了原始的System.Messaging编程接口。然而,在一些情况下,如果你需要在原始MSMQ应用程序和基于WCF的应用程序之间建立通信,有另外一种内置的绑定叫做MsmqIntegrationBinding,它适用于这样的场景。
更多…
还可以看一看这篇文章获得更多信息:
How to: Exchange Messages with WCF Endpoints and Message Queuing Applications
http://msdn.microsoft.com/en-us/library/ms789008.aspx.
通过双重绑定建立发布-订阅服务
发布-订阅是一种常用的设计模式,被广泛用在客户端/服务器通信应用程序。在WCF服务开发中,发布-订阅模式也常用于这样的场景,服务端应用程序向特定的一组对服务端感兴趣的客户端显示数据,数据通过主动推送的方式提供给客户端(而不是由客户端轮询)。这一节将演示如何通过双重绑定实现一个发布-订阅模式的WCF服务。
准备工作
发布-订阅模式在各种应用程序开发场景中被广泛采用,对这个模式有许多不同种类的说明。更多信息可参考下面的链接:
- Publish/subscribe
http://en.wikipedia.org/wiki/Publish/subscribe - Observer pattern
http://en.wikipedia.org/wiki/Observer_pattern
怎么做…
为了实现发布-订阅模式,我们需要在WCF服务开发的各个部分进行特殊的定制,包括一个服务契约设计,绑定配置和服务端寄宿/消费的编码。让我们一起通过以下步骤来建立一个典型的基于列表的发布-订阅服务:
- 首先要做的是设计一个支持发布-订阅模式的服务契约。下面的IPublicBillboard接口表示一个简单契约来向对服务感兴趣的客户端应用程序发布公告。
[ServiceContract(SessionMode = SessionMode.Required,CallbackContract = typeof(IClientReceiver))] public interface IPublicBillboard { [OperationContract(IsInitiating = true)] void Subscribe(); [OperationContract(IsTerminating = true)] void Unsubscribe(); [OperationContract(IsOneWay = true)] void Announce(string msg); } public interface IClientReceiver { [OperationContract(IsOneWay = true)] void GetAnnouncement(string msg); }
在前面的服务契约中,除了主要的契约接口,我们还需要提供一个CallbackContract类型,它被服务端用来主动通知客户端。另外,可用的会话(session)可以使服务端通过会话Id(sessionId)来识别客户端回调通道。
-
实现了服务契约,订阅方法将通过客户端的sessionId缓存回调通道来通知客户端(通过回调通道)。
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] public class PublicBillboard : IPublicBillboard { object _syncobj = new object(); Dictionary<string, IClientReceiver> _receivers = new Dictionary<string, IClientReceiver>(); ...... public void Subscribe() { string sessionId = OperationContext.Current.SessionId; lock (_syncobj) { if (_receivers.Keys.Contains(sessionId)) { _receivers.Remove(sessionId); } _receivers.Add(sessionId, OperationContext.Current.GetCallbackChannel<IClientReceiver>()); } }
前面提到的订阅方法,从OperationContext获得客户端sessionId,并通过sessionId缓存客户端的回调通道。如果已经有一个使用了相同sessionId的回调接口,代码将移除首先存在的那个。 -
在客户端,我们需要提供实现了回调接口的类型,并在初始化服务代理的时候提供这个类型的实例,具体做法如下:
public partial class MainForm : Form, BillboardProxy.IPublicBillboardCallback { BillboardProxy.PublicBillboardClient _boardclient = null; private void MainForm_Load(object sender, EventArgs e) { // Subscribe for the Billboard service _boardclient = new BillboardProxy.PublicBillboardClient( new InstanceContext(this) ); _boardclient.Subscribe(); btnSubmit.Enabled = true; } // Implement the callback interface void BillboardProxy.IPublicBillboardCallback.GetAnnouncement(string msg) { UpdateText(msg); } }
MainForm类实现了回调接口并通过构造函数被指定了客户端代理。 -
最后,服务操作会有选择的主动通知一些或者全部客户端(参考下面的Announce操作):
public void Announce(string msg) { // Enumerate all the client callback channels foreach (string key in _receivers.Keys) { IClientReceiver receiver = _receivers[key]; receiver.GetAnnouncement( string.Format(“{0} announced: {1}”, sessionId, msg)); } }
原理…
通过InstanceContextMode=InstanceContextMode.Single修饰的服务类,可以使所有客户端分享相同的服务实例。可用的会话使得服务端能够通过sessionId区分客户端。下面的截图显示的是服务端控制台,打印出从客户端获取的所有新的公告。
通过调用回调通道接口(从每一个客户端的OperationContext),服务端主动推送新的公告数据到所有客户端。在客户端,回到函数简单的更新论坛UI并在文本框控件中打印新的公告即可。
更多…
两边的服务回调操作都被标记为one-way风格。这样确保了服务操作在被按钮单击事件直接调用时,不会被客户端UI线程阻塞。
如果感兴趣的话,你还可以使用异步服务调用来避免这些类型的线程阻塞问题。
Posted by Bryce Zhang
版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0