[WCF]从WCF生成的WSDL学习笔记(一)

  使用WCF发布的Web服务可以被各种技术平台远程调用,关键就是WCF发布了符合业界标准的WSDL(Web Service Description Language),各种技术平时使用各自的工具将这种WSDL解释成自身所能接受的编程对象,让后对其进行服务调用。本系列文章旨在学习控制WCF生成WSDL。

  为了实验之用,我首先写了一个很简单的WCF应用程序,同样由契约类库、服务实现类库、服务宿主程序、客户端程序组成。(项目文件在下载区,文章中只贴出关键代码)

  

2010050920233421.jpg

以下是服务契约的定义:

ContractedBlock.gif ExpandedBlockStart.gif 代码
namespace WCF_Study3.Contracts
{
[ServiceContract]
public interface IContract
{
[OperationContract]
void NoArgsOperation();

[OperationContract]
void OneArgOperation( int arg1);

[OperationContract]
void TwoArgOperation( int arg1, string arg2);

[OperationContract]
void MultiArgsOperation( params double [] args);

[OperationContract]
object OperationWithReturnArg( object argObj1);
}
}

契约中定义的方法签名都是一些比较典型的例子,不过在这里没有涉及以自定义类型为参数或返回值的方法签名,因为我还不想马上就要进行数据契约的内容。注意到我在以上的例子中没有添加任何附加的WSDL元数据(例如Name、Namespace等等),这些元数据在后面加入,我觉得这样可以更加清晰地看出这些元数据在WCF中被加入后,如何反映到WSDL。我对这个服务契约的实现非常简单(几乎跟没实现一样。。),因此就不发出来了,同样,部署服务宿主的代码暂时与本文主题无关,因此也不发了,如果想要知道怎么部署服务细节,请参考我的其他文章。以下就是这个服务契约生成的部分WSDL(顺带一句,本人愚钝,想了快10分钟才知道怎么可以看到WSDL的内容,因此在这里分享一下,就是在浏览器中输入发布元数据的URL。。):

ContractedBlock.gif ExpandedBlockStart.gif 代码
< wsdl:portType name ="IContract" >
< wsdl:operation name ="NoArgsOperation" >
< wsdl:input wsaw:Action ="http://tempuri.org/IContract/NoArgsOperation"
message ="tns:IContract_NoArgsOperation_InputMessage" />
< wsdl:output wsaw:Action ="http://tempuri.org/IContract/NoArgsOperationResponse "
message ="tns:IContract_NoArgsOperation_OutputMessage" />
</ wsdl:operation >
< wsdl:operation name ="OneArgOperation" >
< wsdl:input wsaw:Action ="http://tempuri.org/IContract/OneArgOperation "
message ="tns:IContract_OneArgOperation_InputMessage" />
< wsdl:output wsaw:Action ="http://tempuri.org/IContract/OneArgOperationResponse "
message ="tns:IContract_OneArgOperation_OutputMessage" />
</ wsdl:operation >
< wsdl:operation name ="TwoArgOperation" >
< wsdl:input wsaw:Action ="http://tempuri.org/IContract/TwoArgOperation"
message ="tns:IContract_TwoArgOperation_InputMessage" />
< wsdl:output wsaw:Action ="http://tempuri.org/IContract/TwoArgOperationResponse "
message ="tns:IContract_TwoArgOperation_OutputMessage" />
</ wsdl:operation >
< wsdl:operation name ="MultiArgsOperation" >
< wsdl:input wsaw:Action ="http://tempuri.org/IContract/MultiArgsOperation "
message ="tns:IContract_MultiArgsOperation_InputMessage" />
< wsdl:output wsaw:Action ="http://tempuri.org/IContract/MultiArgsOperationResponse "
message ="tns:IContract_MultiArgsOperation_OutputMessage" />
</ wsdl:operation >
< wsdl:operation name ="OperationWithReturnArg" >
< wsdl:input wsaw:Action ="http://tempuri.org/IContract/OperationWithReturnArg "
message ="tns:IContract_OperationWithReturnArg_InputMessage" />
< wsdl:output wsaw:Action ="http://tempuri.org/IContract/OperationWithReturnArgResponse "
message ="tns:IContract_OperationWithReturnArg_OutputMessage" />
</ wsdl:operation >
</ wsdl:portType >

这就是全部定义了吗?它甚至连每个服务操作的签名信息都不齐全呢,很明显这不是全部的内容,更多细节的WSDL马上就发出来,但在此之前,首先看下这部分的WSDL有什么有趣的地方也好。

  1.最外层的节点<wsdl:portType>,在上面的WSDL中,它只有一个属性name被设置为"IContract”,没错,这个节点就是代表这我所发布的服务契约,而它的name属性被默认的设置为接口的直接名称(而不是连同命名空间的完整名称)。

  2.在portType节点内部的5个<wsdl:operation>节点,很明显,它们的name属性的值,就告知所有人,它们代表着我在服务契约中定义的5个被公开做服务操作的方法。 

  3.在operation节点里面的<wsdl:input>和<wsdl:output>节点。在WSDL中,这两个节点的顺序就可以代表着一种消息交换模式(Message Exchange Pattern MEP),例如这里使用的是一个input,一个output的标准请求-响应模式。这个定义就意味着,当调用这个服务操作的时候,会首先从客户端发送一个"input"消息;操作结束后服务端会生成一个"output"消息,并返回到客户端,往后还会看到其他的MEP,但它们都是利用这些input、output节点的定义来说明的。

  4.无论是input还是output节点,都设置了Action属性,这个属性很重要,它代表了调用服务过程中,往来的消息(可能从Server到Client,也可能相反)各自代表了怎么样的“动作”。例如OperationWithReturnArg这个操作,当一个发送到服务端的消息的首部设置有Action="http://tempuri.org/IContract/OperationWithReturnArg"的时候,WCF知道如果把该消息分发到相应的服务操作中,这也就说明了,这个Action首部可以被用于消息筛选。而作为响应的消息,它的Action首部的值被默认设置为:请求消息的Action首部值 + "Response"。

  同样在input和output节点中,均有一个message属性,被设置为:"tns:<portType名称>_<operation名称>_<(Input/Ouput)Message>",这是什么呢?这就是对定义这些操作的细节的XML Schema的引用。接下来就发出这些XML Schema的内容:(为了避免太难看,我以操作为单位,逐个发出它们的XML Schema代码段,but keep it in mind,这些代码是在同一份XML Schema文件中的。)

   首先是NoArgsOperation,它的XML Schema代码是最简单的,因为没有返回值,也没有参数:

[OperationContract]
void NoArgsOperation();
ContractedBlock.gif ExpandedBlockStart.gif 代码
< xs:element name ="NoArgsOperation" >
< xs:complexType >
< xs:sequence />
</ xs:complexType >
</ xs:element >
< xs:element name ="NoArgsOperationResponse" >
< xs:complexType >
< xs:sequence />
</ xs:complexType >
</ xs:element >

这段XML代码中,定义了两个<xs:element>节点,从它们的name属性马上就可以推断出,它们是对应操作NoArgsOperation的一种XML自定义元素(我先把它们分别称作负载元素)。这两个负载元素中没有任何有趣的内容,因为这个操作本来就没有返回值和参数。

  接下来看一下为OneArgOperation操作所定义的XML元素:

[OperationContract]
void OneArgOperation( int arg1);
ContractedBlock.gif ExpandedBlockStart.gif 代码
< xs:element name ="OneArgOperation" >
< xs:complexType >
< xs:sequence >
< xs:element minOccurs ="0" name ="arg1" type ="xs:int" />
</ xs:sequence >
</ xs:complexType >
</ xs:element >
< xs:element name ="OneArgOperationResponse" >
< xs:complexType >
< xs:sequence />
</ xs:complexType >
</ xs:element >

这个操作只有一个参数,同样没有返回值。在代表请求负载的负载元素中的<xs:sequence>孙节点中(因为中间还有个<xs:complexType)子节点),又出现了一个XML自定义元素(这个我称作参数元素),参数元素有三个属性——minOccurs、name、type,回顾一下前面的服务契约C#代码,就会知道,这三个属性都是对操作方法中参数的描述,分别是:请求消息中,代表该参数的XML节点至少出现多少个、该参数的名称(很明显默认是使用了程序代码中的名称arg1)、该参数的类型。这里有一个很奇怪的地方,那就是允许参数最少出现的次数为0,而事实上这个参数是必须要提供的,在生成的客户端代理中,也必须提供这个参数。

  接下来是两个参数的操作TwoArgOperation对应的消息元素定义:

[OperationContract]
void TwoArgOperation( int arg1, string arg2);
ContractedBlock.gif ExpandedBlockStart.gif 代码
< xs:element name ="TwoArgOperation" >
< xs:complexType >
< xs:sequence >
< xs:element minOccurs ="0" name ="arg1" type ="xs:int" />
< xs:element minOccurs ="0" name ="arg2" nillable ="true" type ="xs:string" />
</ xs:sequence >
</ xs:complexType >
</ xs:element >
< xs:element name ="TwoArgOperationResponse" >
< xs:complexType >
< xs:sequence />
</ xs:complexType >
</ xs:element >

从该操作的负载元素定义可以看出,<xs:sequence>节点所包含的就是负载携带的参数的列表。这个操作中,arg2的CLR类型为string,由于默认可以为null,因此设置了参数元素的nillable属性为true。这个操作在响应消息元素中同样没有什么有趣的地方。

  下面是MultiArgsOperation操作的消息元素定义:

[OperationContract]
void MultiArgsOperation( params double [] args);
ContractedBlock.gif ExpandedBlockStart.gif 代码
< xs:element name ="MultiArgsOperation" >
< xs:complexType >
< xs:sequence >
< xs:element minOccurs ="0"
          name ="args"
nillable ="true"
type ="q1:ArrayOfdouble"
xmlns:q1 ="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</ xs:sequence >
</ xs:complexType >
</ xs:element >
< xs:element name ="MultiArgsOperationResponse" >
< xs:complexType >
< xs:sequence />
</ xs:complexType >
</ xs:element >

在服务契约的C#代码中,我使用了C#的params关键字来定义该操作的参数可以为任意多个double类型的值。其实最后这个可变长参数只是作为一个数组来使用(或者说它其实就是一个数组),当然这在元数据中需要特殊对待,但在编程的时候真的使用一个数组参数没什么区别。因此,在XML中定义的负载元素,也是把该参数元素的类型定义为一个"ArrayOfdouble",这个名字很奇怪,很明显,它不是微软提供的XML预定义数据类型,那这样就必定在某处WSDL中存在这个XML数据类型的定义。关于这个内容,在后面再研究。

  继续看下一个操作——OperationWithReturnArg:

[OperationContract]
object OperationWithReturnArg( object argObj1);
ContractedBlock.gif ExpandedBlockStart.gif 代码
< xs:element name ="OperationWithReturnArg" >
< xs:complexType >
< xs:sequence >
< xs:element minOccurs ="0"
            name ="argObj1"
nillable ="true"
type ="xs:anyType" />
</ xs:sequence >
</ xs:complexType >
</ xs:element >
< xs:element name ="OperationWithReturnArgResponse" >
< xs:complexType >
< xs:sequence >
< xs:element minOccurs ="0"
name ="OperationWithReturnArgResult"
nillable ="true"
type ="xs:anyType" />
</ xs:sequence >
</ xs:complexType >
</ xs:element >

这个操作是具有一个参数和CLR的object类型返回值的,参数已经没有什么值得研究的地方,看下该操作的响应负载元素的定义,所携带的参数的类型为xs:anyType,在CLR中,object类型是一切类型的基类,任何类型(值类型或引用类型)的对象都可以安全转换为object类型来处理,因此在XML标准数据类型中,可以使用这个anyType来代表object类型的数据。 

  值得一提的是,服务操作的返回值的定义方式,跟之前看到的操作参数定义方式完全一样。前面已经提到,WSDL标准中,使用input、output两个XML元素来表示一个服务操作的MEP,所以尽管我在服务契约的C#代码中只是定义了一个方法,它具有参数和返回值,但参数跟返回值只是程序语言上的概念;当我为服务操作定义的MEP是请求-响应模式时,在WSDL中就已经把这个单一的方法分解成两个消息传输模式(一个input,一个output),无论是哪个消息传输模式,它所代表的都是消息的传输,而对于消息来说,没有所谓的返回值,只有携带的参数。因此在服务操作对应的负载元素(请求消息负载和响应消息负载)定义中,操作中的参数和返回值都是以一致的方式被定义,因为它们都只是消息所携带的参数(内容)。

  还记得刚才的"ArrayOfdouble" ,现在来研究下这个数据类型到底怎么出来的。相信有XML基础的读者都知道,XML具有高扩展性,可以自定义各种标记和XML数据类型,而通常由XML Schema来描述XML文件中各种标记或XML数据类型的细节。那么在所生成WSDL中,在哪里引用外部的(或者生成的)XML Schema呢?先看下下面的WSDL代码:

ContractedBlock.gif ExpandedBlockStart.gif 代码
< wsdl:types >
< xsd:schema targetNamespace ="http://tempuri.org/Imports" >
< xsd:import schemaLocation ="http://localhost:8090/mex?xsd=xsd0" namespace ="http://tempuri.org/" />
< xsd:import schemaLocation ="http://localhost:8090/mex?xsd=xsd1" namespace ="http://schemas.microsoft.com/2003/10/Serialization/" />
< xsd:import schemaLocation ="http://localhost:8090/mex?xsd=xsd2" namespace ="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</ xsd:schema >
</ wsdl:types >

在<wsdl:types>节点中定义了所有该WSDL所引用的XML Schema,其中包括根据服务契约生成的XML Schema(上面代码中的第一个<xsd:import>)和微软预定义的XML Schema(上面代码中的后两个<xsd:import>)。同时这个被定义的XML Schema通过targetNamespace属性,被导入到http://tempuri.org/Imports命名空间中。命名空间为http://tempuri.org/的XML Schema文件其实就是定义之前所有负载元素的文件,里面根据每个服务操作的MEP,定义所需的负载元素。而第二个<xsd:import>所导入的XML Schema就是微软定义的一些基础XML数据类型。而最后一个<xsd:import>所导入的XML Schema就是"ArrayOfdouble"的定义,下面发出其代码:

ContractedBlock.gif ExpandedBlockStart.gif 代码
<? xml version="1.0" encoding="utf-8" ?>
< xs:schema elementFormDefault ="qualified"
      xmlns:xs =http://www.w3.org/2001/XMLSchema
      xmlns:tns ="http://schemas.microsoft.com/2003/10/Serialization/Arrays" >
< xs:complexType name ="ArrayOfdouble" >
< xs:sequence >
< xs:element minOccurs ="0" maxOccurs ="unbounded" name ="double" type ="xs:double" />
</ xs:sequence >
</ xs:complexType >
< xs:element name ="ArrayOfdouble" nillable ="true" type ="tns:ArrayOfdouble" />
</ xs:schema >

一个值得注意的地方是,这个XML Schema定义了"ArrayOfdouble"后,是把它的定义放入到http://schemas.microsoft.com/2003/10/Serialization/Arrays中,而不是本服务的命名空间中(例如http://tempuri.org/)。

  前面说过,WCF自动为每个服务操作生成对应的负载元素,那在WSDL中是怎么把服务操作元素<wsdl:operation>与负载元素<xs:element>关联起来呢?答案是通过<wsdl:message>元素。以MultiArgsOperation为例:

ContractedBlock.gif ExpandedBlockStart.gif 代码
< wsdl:operation name ="MultiArgsOperation" >
< wsdl:input wsaw:Action =http://tempuri.org/IContract/MultiArgsOperation
           message ="tns:IContract_MultiArgsOperation_InputMessage" />
< wsdl:output wsaw:Action =http://tempuri.org/IContract/MultiArgsOperationResponse
           message ="tns:IContract_MultiArgsOperation_OutputMessage" />
</ wsdl:operation >


< wsdl:message name ="IContract_MultiArgsOperation_InputMessage" >
< wsdl:part name ="parameters" element ="tns:MultiArgsOperation" />
</ wsdl:message >
< wsdl:message name ="IContract_MultiArgsOperation_OutputMessage" >
< wsdl:part name ="parameters" element ="tns:MultiArgsOperationResponse" />
</ wsdl:message >


< xs:element name ="MultiArgsOperation" >
< xs:complexType >
< xs:sequence >
< xs:element minOccurs ="0"
name ="args"
nillable ="true"
type ="q1:ArrayOfdouble"
xmlns:q1 ="http://schemas.microsoft.com/2003/10/Serialization/Arrays" />
</ xs:sequence >
</ xs:complexType >
</ xs:element >
< xs:element name ="MultiArgsOperationResponse" >
< xs:complexType >
< xs:sequence />
</ xs:complexType >
</ xs:element >

从上面的WSDL代码,简单总结就是:服务操作定义了消息的输入管道元素、输出管道元素及其顺序;每个输入/输出管道元素引用了一个消息元素;每个消息元素包装着它的负载元素。

  这篇文章还没有提及对各种契约进行元数据配置的情况,还记得我用的服务契约(包括其操作契约)是完全没有经过定制配置的。下一篇文章将尝试进行一些定制配置,然后研究一下WSDL会有什么相应的变化。

转载于:https://www.cnblogs.com/klzwj1988/archive/2010/05/10/1731291.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值