定义服务契约
构建HelloWCF应用的第一步是创建服务契约。契约式是表示消息应用外形的主要方式。对于外形,是指服务暴露的操作,使用的消息schema和每个操作实现的消息交换模式(MEP)。总之,契约定义了我们消息应用生成和使用的东西。大多数契约是带有WCF API定义的属性标记的类型定义。
在下面的例子里,服务契约是一个带有System.ServiceModel.ServiceContractAttribute 和System.ServiceModel.OperationContractAttribute标记的接口,如下所示:
[ServiceContract] public interface IHelloWCF { [OperationContract] void Say(String input); }
在高层次上,我们的服务契约表示我们接收消息的应用包含一个名字为Say的操作,并且这个操作接收一个String类型的参数和void返回类型。发送消息的应用可以用它来构造和发送消息给接收程序。既然我们已经定义的服务契约,那就到了该定义接收程序侦听地址和如何与其它消息参与者交换消息的时候了。
定义地址和绑定
定义侦听请求消息的地址需要使用System.Uri类型,定义如何与其它消息参与者交换消息需要我们使用System.ServiceModel.Channels.Binding类型。或者这些类型的继承类型。下面的代码说明了如何在我们的应用里使用Uri和Binding类型。
static void Main(){ // define where to listen for messages定义侦听消息的地址 Uri address = new Uri("http://localhost:8000/IHelloWCF"); // define how to exchange messages定义如何交换消息 BasicHttpBinding binding = new BasicHttpBinding(); }
注意局部变量address使用的是HTTP格式的统一资源标识符(URI)。选择这个地址强制要求我们使用HTTP传输。更高层次上,绑定是指定传输、消息编排和消息编码的主要方式。局部变量binding是BasicHttpBinding类型的实例。和你从名字看到的一样,BasicHttpBinding创建的是一个用于HTTP传输的消息架构。
创建一个终结点并启动侦听
接下来我们要使用地址(address)、绑定(binding)和契约(contract)来构建一个终结点(endpoint)并在此终结点上侦听发送进来的消息。通常来说,一个WCF接受程序可以构建和使用多个终结点,并且每个终结点都需要一个地址、一个绑定和一个契约。System.ServiceModel.ServiceHost类型构建和托管终结点,并管理接受应用底层结构的其他部分,比如线程和对象的生命周期。下面代码块演示了如何实例化ServiceHost,如何添加终结点和如何开始侦听进入的消息:
static void Main(){ // define where to listen for messages定义侦听消息的地址 Uri address = new Uri("http://localhost:4000/IHelloWCF"); // define how to exchange messages定义如何交换消息 BasicHttpBinding binding = new BasicHttpBinding(); // instantiate a ServiceHost, passing the type to instantiate实例化ServiceHost,传递服务类型 // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // add an endpoint, passing the address, binding, and contract增加终结点、绑定和契约 svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address); // begin listening开始侦听 svc.Open(); // indicate that the receiving application is ready and指示应用准备接受消息 // keep the application from exiting immediately保持应用程序不会立即退出 Console.WriteLine("The HelloWCF receiving application is ready"); Console.ReadLine(); // close the service host关闭宿主 svc.Close(); }
上述代码里调用了svc.AddServiceEndpoint 和svc.Open。AddServiceEndpoint实例方法设置ServiceHost对象的属性,这样它将使用地址、绑定和契约参数执行的行为来侦听消息。要着重指出的是AddServiceEndpoint方法没有开始循环侦听;它仅仅是简单地改变了ServiceHost对象的状态。ServiceHost实例的Open方法构建了消息基础结构,并开始循环侦听。Open方法会验证ServiceHost对象的状态,从它的状态里构建终结点,并且开始侦听。
映射接收的消息到HelloWCF的成员
在目前状态,我们编译程序,当程序试图构建一个终结点的时候,会出现一个异常:InvalidOperationException。原因一目了然:在ServiceHost类型的构造函数里,我们传递了HelloWCF作为参数,因此,这就表示消息基础结构要分发消息给我们的HelloWCF对象。因此,必然存在消息到服务成员的映射关系。最简单的创建映射的方式就是使HelloWCF服务类实现服务契约IHelloWCF。
using System; using System.ServiceModel; using System.ServiceModel.Channels; // implement the IHelloWCF service contract sealed class HelloWCF : IHelloWCF { // indicate when a HelloWCF object is created HelloWCF() { Console.WriteLine("HelloWCF object created"); } static void Main(){ // define where to listen for messages Uri address = new Uri("http://localhost:4000/IHelloWCF"); // define how to exchange messages BasicHttpBinding binding = new BasicHttpBinding(); // instantiate a ServiceHost, passing the type to instantiate // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // add an endpoint, passing the address, binding, and contract svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address); // begin listening svc.Open(); // indicate that the receiving application is ready and // keep the application from exiting immediately Console.WriteLine("The HelloWCF receiving application is ready"); // wait for incoming messages Console.ReadLine(); // close the service host svc.Close(); } // received messages are dispatched to this instance // method as per the service contract public void Say(String input){ Console.WriteLine("Message received, the body contains: {0}", input); } } [ServiceContract] public interface IHelloWCF { [OperationContract] void Say(String input); }
改变HelloWCF的类型定义会使得消息的基础结构分发接受到的消息到服务实例的Say操作上,因此会在控制台界面上输出一个简单的语句。
编译、运行和检验接受者
我们现在准备使用下面的命令行编译并运行这个应用:
C:\temp>csc /nologo /r:"c:\WINDOWS\Microsoft.Net\Framework\v3.0\Windows Communication Foundation\System.ServiceModel.dll" HelloWCFApp.cs C:\temp>HelloWCFApp.exe The HelloWCF receiving application is ready
此时,接受消息的应用在被动地等待请求消息的到来。我们是用netstat.exe可以检查一下应用是否确实在侦听,如下所示:
c:\temp>netstat –a –b TCP kermit:4000 0.0.0.0:0 LISTENING 1104 [HelloWCFApp.exe]
向接受者发送消息
发送消息的基础结构也需要依靠地址、绑定和契约,这与接收消息的基础结构类似。
与接受者不同,绝大多数发送者放弃直接使用Uri类型,而是使用System.ServiceModel.EndpointAddress类型去表示消息发送的目标。
EndpointAddress类型是WCF对于WS-Addressing 终结点参考的抽象。此外,发送者不使用ServiceHost类型,而是使用ChannelFactory<T>类型(T是服务契约类型)。ChannelFactory<T>类型构建发送消息的基础结构和ServiceHost构建接受消息的基础结构类似。下面的代码演示了如何使用EndpointAddress类型和ChannelFactory<T>构建发送基础结构。
using System; using System.ServiceModel; using System.ServiceModel.Channels; // implement the IHelloWCF service contract sealed class HelloWCF : IHelloWCF { // indicate when a HelloWCF object is created HelloWCF() { Console.WriteLine("HelloWCF object created"); } static void Main(){ // define where to listen for messages Uri address = new Uri("http://localhost:4000/IHelloWCF"); // define how to exchange messages BasicHttpBinding binding = new BasicHttpBinding(); // instantiate a ServiceHost, passing the type to instantiate // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // add an endpoint, passing the address, binding, and contract svc.AddServiceEndpoint(typeof(IHelloWCF), binding, address); // begin listening svc.Open(); // indicate that the receiving application is ready and // keep the application from exiting immediately Console.WriteLine("The HelloWCF receiving application is ready"); // begin the sender code发送者代码开始 // create a channelFactory<T> with binding and address ChannelFactory<IHelloWCF> factory = new ChannelFactory<IHelloWCF>(binding, new EndpointAddress(address)); // use the factory to create a proxy使用工厂创建代理 IHelloWCF proxy = factory.CreateChannel(); // use the proxy to send a message to the receiver使用代理发送消息给接受者 proxy.Say("Hi there WCF"); // end the sender code发送者代码结束 Console.ReadLine(); // close the service host svc.Close(); } // received messages are dispatched to this instance // method as per the service contract public void Say(String input){ Console.WriteLine("Message received, the body contains: {0}", input); } } [ServiceContract] public interface IHelloWCF { [OperationContract] void Say(String input); }
更高层次上,ChannelFactory<T>对象是一个可以制造产生和发送消息给接受者(因此需要在构造函数里传递绑定和地址)的基础结构的类型。ChannelFactory<T>实例的CreateChannel方法实际创建的是发送基础结构,并且通过实现服务契约的一个对象返回这个基础结构的引用。
编译、运行和检验发送者
既然我们已经完成了发送和接受基础结构代码,现在应该是编译和运行程序的时候了,如下所示:
c:\temp>csc /nologo /r:"c:\WINDOWS\Microsoft.Net\Framework\v3.0\Windows Communication Foundation\System.ServiceModel.dll" HelloWCFApp.cs c:\temp>HelloWCFApp.exe The HelloWCF receiving application is ready HelloWCF object created Message received, the body contains: HelloWCF!
如期望的一样,我们的程序执行步骤如下:
1. 为接受来自http://localhost:4000/IHelloWCF的消息构建基础结构。
2. 开始在http://localhost:4000/IHelloWCF上侦听消息。
3. 构建发送到http://localhost:4000/IHelloWCF消息的基础结构。
4. 生成和发送消息到http://localhost:4000/IHelloWCF。
5. 接受消息,实例化一个HelloWCF对象,分发消息到服务实例的Say方法上。
看一下消息
现在代码写完了,貌似没看到我们HelloWCF例子里哪里使用到了消息。对于开发者,一个WCF应用看起来和感觉都很像面向对象或者面向组件的应用。在运行时,WCF应用要生成、发送和接受消息,同样也要处理消息。通过修改Say方法的实现我们能看到WCF接触结构的消息:
public void Say(String input){ Console.WriteLine("Message received, the body contains: {0}", input); // Show the contents of the received message显示接受消息内容 Console.WriteLine( OperationContext.Current.RequestContext.RequestMessage.ToString()); }
修改Say方法后的输出如下:
The HelloWCF receiving application is ready HelloWCF object created Message received, the body contains: HelloWCF! <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <To s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/adessing/none"> http://localhost:8000/IHelloWCF </To> <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none"> http://tempuri.org/IHelloWCF/Say </Action> </s:Header> <s:Body> <Say xmlns="http://tempuri.org/"> <input>HelloWCF!</input> </Say> </s:Body> </s:Envelope>
注意到打印的SOAP消息,消息的Body部分包含我们传递给局部变量的channel 上Say方法的字符串。宏观上讲,我们的应用程序使用这个字符来构建一个SOAP消息,然后发送这个SOAP消息到我们程序的接受部分。接受部分,换句话说,它要接受SOAP消息,创建一个HelloWCF实例,提取SOAP Body的内容,调用HelloWCF 实例的Say方法,传递字符串参数。
暴露元数据
我们的Hello WCF程序使用一个相当简单的方法就实现了接收者和发送者之间的兼容性。因为接收者和发送者驻留在同一个AppDomain里,并且接收者使用的对象对于发送者来说是可见的,我们在发送者代码里简单地重用了地址、绑定和契约。在发部分消息应用里,这个方法是可行的。绝大多数情况,我们希望发送者和接收者去驻留在不同的机器上的AppDomains里。在这些场景里,接收者显示指定消息需求,发送者遵守这些需求。
WS- MetadataExchange规范规定了发送者和接收者在平台无关时如何交换这些数据信息。在更多的条款里,WS-MetadataExchange规范限定了方便终结点之间进行消息交换的schema和编排。在大部分现实世界的应用系统里,有一个暴露信息方式的需求,这个方式就是发送者询问接收者的终结点去提取元数据,并使用这些元数据构建能发送给接收终结点消息的基础结构。
WCF缺省的情况下不会暴露元数据,这些原因都是因为对安全的考虑。元数据暴露的信息包含应用系统的安全需求。以保护秘密的名义,这个团队选择缺省情况下关闭这个特性。如果决定暴露系统的元数据,我们可以构建一个暴露元数据的终结点。构建元数据终结点的方式和其它终结点非常相似:使用地址、绑定和契约。但是目前为止你看到的终结点不太一样,就是服务契约已经定义到WCF的API里了。
构建元数据终结点的第一步是修改ServiceHost到可以托管元数据的状态。我们可以通过System.ServiceModel. Description.ServiceMetadataBehavior对象增加到ServiceHost行为集合里。行为是WCF基础结构用来改变本地消息处理的特定信息。下面代码演示了如何增加ServiceMetadataBehavior对象到活动的ServiceHost对象:
// instantiate a ServiceHost, passing the type to instantiate // when the application receives a messageServiceHost svc = new ServiceHost(typeof(HelloWCF), address); // BEGIN NEW METADATA CODE // create a ServiceMetadataBehavior创建服务元数据行为 ServiceMetadataBehavior metadata = new ServiceMetadataBehavior(); metadata.HttpGetEnabled = true; // add it to the servicehost description svc.Description.Behaviors.Add(metadata);
下一步就是为元数据终结点定义Binding。元数据绑定的对象模型与其它绑定区别很大,我们通过调用工厂方法上的System.ServiceModel.Description.MetadataExchangeBindings的类型创建元数据绑定,如下所示:
// instantiate a ServiceHost, passing the type to instantiate // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // BEGIN NEW METADATA CODE // create a ServiceMetadataBehavior创建服务元数据行为 ServiceMetadataBehavior metadata = new ServiceMetadataBehavior(); // add it to the servicehost description svc.Description.Behaviors.Add(metadata); // create a TCP metadata binding创建TCP元数据绑定 Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding(); 由于ASMX(ASP.NET Web Service)的影响,你也许会认为元数据只能通过HTTP传输呈现。事实上,元数据可以通过多种传输协议传递,并且WS-MetadataExchange说明了这个灵活性。在我们的例子里,我们调用CreateMexTcpBinding方法,它返回了一个继承自Binding类型的TCP 传输绑定。因为我们使用的是TCP传输,所以我们必须确保元数据地址使用了TCP地址格式,如下所示: // instantiate a ServiceHost, passing the type to instantiate // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // BEGIN NEW METADATA CODE // create a ServiceMetadataBehavior ServiceMetadataBehavior metadata = new ServiceMetadataBehavior(); // add it to the servicehost description svc.Description.Behaviors.Add(metadata); // create a TCP metadata binding Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding(); // create an address to listen on WS-Metadata exchange traffic //创建元数据交换侦听地址 Uri mexAddress = new Uri("net.tcp://localhost:5000/IHelloWCF/Mex");
既然我们定义了元数据终结点需要的地址和绑定,我们要添加终结点到ServiceHost上,方式很像我们定义的第一个消息终结点。当添加元数据终结点时,我们要使用WCF API定义的名为System.ServiceModel.Description.IMetadataExchange的服务契约。下面代码演示了如何添加一个元数据终结点到ServiceHost上,使用适当的地址、绑定和契约。
// instantiate a ServiceHost, passing the type to instantiate // when the application receives a message ServiceHost svc = new ServiceHost(typeof(HelloWCF)); // BEGIN NEW METADATA CODE // create a ServiceMetadataBehavior ServiceMetadataBehavior metadata = new ServiceMetadataBehavior(); // add it to the servicehost description svc.Description.Behaviors.Add(metadata); // create a TCP metadata binding Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding(); // create an address to listen on WS-Metadata exchange traffic Uri mexAddress = new Uri("net.tcp://localhost:5000/IHelloWCF/Mex"); // add the metadata endpoint添加元数据终结点 svc.AddServiceEndpoint(typeof(IMetadataExchange), mexBinding, mexAddress); // END METADATA CODE
使用元数据
Microsoft .NET Framework SDK安装了一个功能强大的工具名字是svcutil.exe,它的一个功能就是询问一个运行的消息应用并基于获得的信息生成代理。从内部来说,svcutil.exe使用的是WS-MetadataExchange协议,像与ASMX一起普及的WSDL里的“get”语义一样。因为我们的接收程序暴露了一个元数据终结点,我们可以把svcutil.exe指向这个运行的终结点,svcutil.exe会自动生成一个代理类型和与服务终结点兼容的配置信息,这些信息都是参考元数据生成。当使用这种方式的时候,svcutil.exe 依照WS-MetadataExchange的方式发送消息给接收程序,并转化这些消息为.NET Framework的类型以方便发送消息程序的开发。
使用Svcutil.exe生成代理
在你运行svcutil.exe以前,检查一下HelloWCFApp.exe正常运行并侦听请求消息。下一步就是打开一个新的Windows SDK命令行窗口,输入下面的命令:
C:\temp>svcutil /target:code net.tcp://localhost:5000/IHelloWCF/Mex
Svcutil.exe会创建2个文件:HelloWCFProxy.cs 和output.config。如果你检查一下HelloWCFProxy.cs文件,你就会看到svcutil.exe产生了一个包含IHelloWCF、IHelloWCFChannel,和HelloWCFClient的代码文件。合起来看,这些类型定义就是帮助我们创建于接受程序兼容的发送代码。