一. 客户端编程 为了调用服务上的操作,客户端首先需要把服务合同导入到客户端的本地描述中。这意味着,该服务需要客户端暴露一个标准方法以检索它的元数据-这是通过让服务暴露一个元数据交换(MEX)端点来实现的。只要服务在宿主上注册至少一个TCP、HTTP或 IPC基地址,那么,这个基于WCF的服务就能够自动地暴露一个MEX端点。
如果客户端使用WCF,那么调用操作的普通方法是使用一个代理。这个代理是一个CLR类,它暴露单个描述服务合同的CLR接口。注意,如果该服务支持若干合同,那么客户端需要对于每一种合同类型都创建一个相应代理。代理负责提供与服务的合同相同的操作,而且还提供其它方法来管理代理的生命周期和到服务的连接。该代理完整地封装该服务的每一个方面:服务的位置,服务的实现技术及其运行时刻平台和通讯传输。
你可以使用Visual Studio 2005来导入服务元数据并且生成一个代理。如果服务是自宿主的,那么首先要启动该服务,然后从客户端工程的上下文菜单中选择"Add Service Reference…"。如果服务宿主在IIS或WAS上,那么就不需要预启动该服务了。
有趣的是,如果服务是自宿主在与客户端工程同一个方案中的另一个工程中,那么你可以在Visual Studio 2005中启动宿主并且仍然添加参考,因为不同于大多数工程设置,这个选项是无法禁止的(见图6)。这会导致调出"Add Service Reference"对话框,你需要在其中提供服务的基地址(或一个基地址和一个MEX URI)以及包含代理的命名空间。
图6.你可以使用Visual Studio 2005生成一个代理。 |
Visual Studio 2005使用SvcUtil.exe命令行工具,并且你可以自己使用它。主要理由是,你可以使用SvcUtil开关所提供的众多的选项。为了直接使用 SvcUtil,你可以提供给它MEX地址并且,作为选择,还可以提供一个代理文件名。默认的代理文件名是实现该服务的服务端类的名字。例如,当把服务 MyService宿主在IIS中时,简单地运行下列命令行:
SvcUtil http://localhost/MyService/MyService.svc /out:Proxy.cs
借助于自宿主,你可以不受限于仅使用HTTP基地址。假定自宿主服务注册这些基地址:
http://localhost:8002 net.tcp://localhost:8003 net.pipe://localhost/MyPipe |
然后,启动宿主,则你可以使用任何下列命令之一来生成该代理:
SvcUtil http://localhost:8002/MEX /out:Proxy.cs SvcUtil http://localhost:8002/ /out:Proxy.cs SvcUtil net.tcp://localhost:8003/MEX /out:Proxy.cs SvcUtil net.pipe://localhost/MyPipe /MEX/out:Proxy.cs |
该代理类没有对服务实现类的参考,而只参考服务暴露的合同。这个代理可以与一个提供地址和绑定的客户端配置文件一起使用,也可以在没有配置文件的情况下使用。注意,每一个代理实例都准确地指向一个端点,与之交互的端点在构造时刻提供给代理。
二. 管理客户端配置 客户端需要知道服务所在地并使用与它的服务相同的绑定;当然,也要以代理的形式导入服务合同。实质上,这与在服务的端点处捕获的信息完全一致。为了反映这一信息,客户端配置文件包含关于目标端点的信息并且甚至使用与宿主相同的模式。
例如,列表6(见本文相应下载源码)显示了与一个服务(其宿主是根据列表2进行配置的)进行交互需要的客户端配置文件。注意,在这个客户端配置文件中的合同类型(和命名空间)是由SvcUtil生成的导入的类型(和命名空间,如果有的话),而不是服务类型和命名空间。该客户端配置文件可以列出与服务支持一样多的端点,并且该客户端可以使用任何其中之一来与该服务交互。列表7(见本文相应下载源码)展示了客户端配置文件-它匹配显示于列表3中的宿主配置文件。
默认地,SvcUtil还自动生成一个客户端配置文件output.config。你可以使用/config开关来指定一个配置文件名:
SvcUtil http://localhost:8002/MyService/MEX/out:Proxy.cs /config:App.Config |
并且,你可以使用/noconfig开关来压缩生成的配置文件:
SvcUtil http://localhost:8002/MyService/MEX/out:Proxy.cs /noconfig |
为了支持进程内宿主,应用程序配置文件应该列出服务和客户端节,见列表8(见本文相应下载源码)。注意,NetNamedPipeBinding被用于进程内调用。
WCF提供一个能够编辑宿主和客户端配置文件的配置文件编辑器SvcConfigEditor.exe(见图7)。在写本文时,SvcConfigEditor只是生成一些不可读的配置文件,因此,在纠正这一问题之前,你应该手工地编辑这个文件。
图7:SvcConfigEditor用于编辑宿主和客户端配置文件。
|
三. 创建和使用代理
SvcUtil生成的代理类派生自类ClientBase<T>,定义为:
public class ClientBase<T> : IDisposable { protected ClientBase(string endpointConfigurationName); protected ClientBase(Binding binding,EndpointAddress remoteAddress); public void Close(); public void Dispose(); protected T InnerProxy{get;} //其它成员 } |
这个InnerProxy属性是客户端需要消费的合同类型,并且SvcUtil生成的ClientBase<T>的子类简单地把它代理到方法调用(见列表5)。客户端需要实例化一个代理对象并且提供给其构造器端点信息-或者是来自配置文件的端点节名(见列表6)或者是在不使用一个配置文件时的端点地址和绑定对象。然后,该客户端可以使用代理方法来调用该服务,并且客户端完成后,它需要关闭该代理实例:
MyContractProxy proxy = new MyContractProxy("MyEndpoint"); proxy.MyMethod(); proxy.Close(); |
关闭代理将终止与服务的会话并且关闭连接。作为选择,你可以使用代理的Dispose()方法来关闭它。Dispose()方法的优点在于,你可以使用using语句来调用它,即使在面临异常处理时:
using(MyContractProxy proxy = new MyContractProxy("MyEndpoint")) { proxy.MyMethod(); } |
在客户端配置文件中的每个合同类型的一个端点可以被指派为一个默认端点。默认端点是一个没有名字标志或只有一个空名("")的端点节:
<system.serviceModel> <client> <endpoint ... contract="IMyContract" /> <endpoint name="OtherEndpoint" ... contract="IMyContract" /> </client> </system.serviceModel> |
一个默认的端点仅仅是一种理想情况;当创建一个针对默认端点的代理时,你可以使用代理的默认构造器来让它使用默认端点:
MyContractProxy proxy = new MyContractProxy(); proxy.MyMethod(); proxy.Close(); |
四. 可编程的客户端配置
不依赖于配置文件,客户端也可以通过编程方式来构建端点并且把它提供给代理构造器。列表9(见本文相应下载源码)展示了这一技术,其中展示了等价于列表 6中的代码(针对列表2中的服务)。编程配置是很有用的:当端点决策或者是完全动态的-基于当前输入或在运行时刻使用;或当决策是静态的且从不更改时,你最好采用硬编码之。
五. WCF架构
到目前为止,本文讨论了建立和消费简单WCF服务所有要求的内容。然而,WCF还为可靠性、事务、安全和实例激活等提供了极其宝贵的支持,所有这些都依赖于WCF基于拦截的架构。
让客户端与代理交互意味着,WCF总是介于服务和客户端之间来拦截调用并且执行预调用和调用后处理。当代理把调用堆栈帧串行化为一条消息并且沿着一个通道链发送消息时,该解释即开始。
每一个客户端通道都要做消息的预调用处理。这个链的正确结构和组成在很大程度上依赖于绑定。例如,其中一个通道负责编码该消息(二进制,文本或 MTOM),一个通道负责传递安全调用上下文,一个通道负责传播客户端事务,一个通道负责管理可靠的会话,一个通道负责加密消息正文(如果这样配置的话),等等。客户端的最后一个通道是传输通道,它负责把消息由经配置的传输传送到宿主。
在宿主端,该消息也经过一个通道链,从而实现宿主端消息的预调用处理。在宿主端的第一个通道是传输通道,它接收来自传输的消息。随后的通道执行各种任务,例如解密消息正文、译解消息、把传播的事务设置到执行线程、设置安全主管、管理会话和激活服务实例。最后一个通道在宿主端把该消息传递到调度器。由调度器把该消息值的转换成一个堆栈帧并且调用服务实例。图8描述了这一顺序。
图8:WCF架构看上去的样子。 |
在客户端和服务端的拦截都要确保客户端和服务得到它们所要求的运行时刻环境以便正确运行。由服务实例执行该调用并且把控制返回到调度器,由它把返回值和错误信息(如果有的话)转换成一条返回消息。现在,整个过程被颠倒:调度器通过宿主端通道传送消息以执行调用后处理,例如管理事务、撤销实例、编码应答信息、加密它,等等。返回的消息转到传输通道,在此把消息发送到客户端通道以便客户端调用后处理:解密,编码,提交或取消事务,等等。
由代理把返回的消息转换成一个堆栈帧并且把控制返回到客户端。最值得注意的是,在这个架构中的几乎所有的点都提供了可扩展性钩子-你可以为专利性行为提供定制通道,定制实例管理或定制安全。事实上,WCF提供的这些标准工具都是使用相同的可扩展性模型实现的。
六. 使用通道 你可以直接使用通道来调用服务上的操作而甚至不必依赖于一个SvcUtil生成的代理。显示于列表10(见本文相应下载源码)中的 ChannelFactory<T>类能够使你任意地创建一个代理。你需要提供给它的构造器端点信息-或者是来自配置文件的端点名,或者是绑定和地址对象,或者是一个端点对象。然后,使用CreateChannel()方法以获得一个到代理的参考(顶级通道)并且使用它的方法。最后,关闭该代理或者通过把它强制转换为一个IDisposable接口并且调用其Dispose()方法或强制转换为一个IClientChannel接口并且调用其Close ()方法:
ChannelFactory<IMyContract> factory; //使用默认的站点 factory = new ChannelFactory<IMyContract>(""); IMyContract proxy1 = factory.CreateChannel(); using(proxy1 as IDisposable) { proxy1.MyMethod(); } IMyContract proxy2 = factory.CreateChannel(); proxy2.MyMethod(); IClientChannel clientChannel = proxy2 as IClientChannel; Debug.Assert(clientChannel != null); clientChannel.Close(); |
七. 总结 WCF是一种用于构建Windows面向服务的应用程序的SDK。它能够让你使用杰出的CLR编程结构(例如类和接口)来发布和消费服务。这种编程模型是声明性的并且大部分是属性驱动的。WCF基于拦截的架构提供了内置的工具用于管理服务的许多运行时刻方面;并且,从其应用前景来看,它也是构建 Windows分布式应用程序的最具生产效率的方式。
WCF的第一个发行版本为开发服务(例如宿主,服务实例管理,异步调用,可靠性,事务管理,非连接性队列调用和安全性)提供了许多有用的工具。在WCF的第二个发行版本中将添加服务发现以及事件出版和订阅。WCF要求.NET 2.0环境并将同Windows Vista一同发行。另外,你可以在Windows XP SP2和Windows Server 2003 SP1环境下使用它。