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

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

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

  

以下是服务契约的定义:

代码
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。。):

代码
<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();
代码
<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);
代码
<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);
代码
<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);
代码
<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);
代码
<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代码:

代码
<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"的定义,下面发出其代码:

代码
<?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为例:

代码
<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会有什么相应的变化。

posted @ 2010-05-10 19:14  DOF_KL  阅读(7960)  评论(2编辑  收藏  举报