Web Service开发实用指南
附录
一、什么是Web Service
1.分布式应用程序和浏览器
研究一下当前的应用程序开发,你会发现一个绝对的倾向:人们开始偏爱基于浏览器的瘦客户应用程序。这当然不是因为瘦客户能够提供更好的用户界面,而是因为它能够避免花在桌面应用程序发布上的高成本。发布桌面应用程序成本很高,一半是因为应用程序安装和配置的问题,另一半是因为客户和服务器之间通信的问题。
传统的Windows富客户应用程序使用DCOM来与服务器进行通信和调用远程对象。配置好DCOM使其在一个大型的网络中正常工作将是一个极富挑战性的工作,同时也是许多IT工程师的噩梦。事实上,许多IT工程师宁愿忍受浏览器所带来的功能限制,也不愿在局域网上去运行一个DCOM。在我看来,结果就是一个发布容易,但开发难度大而且用户界面极其受限的应用程序。极端的说,就是你花了更多的资金和时间,却开发出从用户看来功能更弱的应用程序。不信?问问你的会计师对新的基于浏览器的会计软件有什么想法:绝大多数商用程序用户希望使用更加友好的Windows用户界面。
关于客户端与服务器的通信问题,一个完美的解决方法是使用HTTP协议来通信。这是因为任何运行Web浏览器的机器都在使用HTTP协议。同时,当前许多防火墙也配置为只允许HTTP连接。
许多商用程序还面临另一个问题,那就是与其他程序的互操作性。如果所有的应用程序都是使用COM或.NET语言写的,并且都运行在Windows平台上,那就天下太平了。然而,事实上大多数商业数据仍然在大型主机上以非关系文件(VSAM)的形式存放,并由COBOL语言编写的大型机程序访问。而且,目前还有很多商用程序继续在使用C++、Java、Visual Basic和其他各种各样的语言编写。现在,除了最简单的程序之外,所有的应用程序都需要与运行在其他异构平台上的应用程序集成并进行数据交换。这样的任务通常都是由特殊的方法,如文件传输和分析,消息队列,还有仅适用于某些情况的的API,如IBM的"高级程序到程序交流(APPC)"等来完成的。在以前,没有一个应用程序通信标准,是独立于平台、组建模型和编程语言的。只有通过Web Service,客户端和服务器才能够自由的用HTTP进行通信,不论两个程序的平台和编程语言是什么。
2.什么是Web Service
对这个问题,我们至少有两种答案。从表面上看,Web Service就是一个应用程序,它向外界暴露出一个能够通过Web进行调用的API。这就是说,你能够用编程的方法通过Web来调用这个应用程序。我们把调用这个Web Service的应用程序叫做客户。例如,你想创建一个Web Service,它的作用是返回当前的天气情况。那么你可以建立一个ASP页面,它接受邮政编码作为查询字符串,然后返回一个由逗号隔开的字符串,包含了当前的气温和天气。要调用这个ASP页面,客户端需要发送下面的这个HTTP GET请求:
http://host.company.com/weather.asp?zipcode=20171
返回的数据就应该是这样:
21,晴
这个ASP页面就应该可以算作是Web Service了。因为它基于HTTP GET请求,暴露出了一个可以通过Web调用的API。当然,Web Service还有更多的东西。
下面是对Web Service更精确的解释:Web Services是建立可互操作的分布式应用程序的新平台。作为一个Windows程序员,你可能已经用COM或DCOM建立过基于组件的分布式应用程序。COM是一个非常好的组件技术,但是我们也很容易举出COM并不能满足要求的情况。
Web Service平台是一套标准,它定义了应用程序如何在Web上实现互操作性。你可以用任何你喜欢的语言,在任何你喜欢的平台上写Web Service,只要我们可以通过Web Service标准对这些服务进行查询和访问。
3.新平台
Web Service平台需要一套协议来实现分布式应用程序的创建。任何平台都有它的数据表示方法和类型系统。要实现互操作性,Web Service平台必须提供一套标准的类型系统,用于沟通不同平台、编程语言和组件模型中的不同类型系统。在传统的分布式系统中,基于界面(interface)的平台提供了一些方法来描述界面、方法和参数(注:如COM和COBAR中的IDL语言)。同样的,Web Service平台也必须提供一种标准来描述Web Service,让客户可以得到足够的信息来调用这个Web Service。最后,我们还必须有一种方法来对这个Web Service进行远程调用。这种方法实际是一种远程过程调用协议(RPC)。为了达到互操作性,这种RPC协议还必须与平台和编程语言无关。下面几个小节就简要介绍了组成Web Service平台的这三个技术。
4.XML和XSD
可扩展的标记语言(XML)是Web Service平台中表示数据的基本格式。除了易于建立和易于分析外,XML主要的优点在于它既是平台无关的,又是厂商无关的。无关性是比技术优越性更重要的:软件厂商是不会选择一个由竞争对手所发明的技术的。
XML解决了数据表示的问题,但它没有定义一套标准的数据类型,更没有说怎么去扩展这套数据类型。例如,整形数到底代表什么?16位,32位,还是64位?这些细节对实现互操作性都是很重要的。W3C制定的XML Schema(XSD)就是专门解决这个问题的一套标准。它定义了一套标准的数据类型,并给出了一种语言来扩展这套数据类型。Web Service平台就是用XSD来作为其数据类型系统的。当你用某种语言(如VB.NET或C#)来构造一个Web Service时,为了符合Web Service标准,所有你使用的数据类型都必须被转换为XSD类型。你用的工具可能已经自动帮你完成了这个转换,但你很可能会根据你的需要修改一下转换过程。
5.SOAP
Web Service建好以后,你或者其他人就会去调用它。简单对象访问协议(SOAP)提供了标准的RPC方法来调用Web Service。实际上,SOAP在这里有点用词不当:它意味着下面的Web Service是以对象的方式表示的,但事实并不一定如此:你完全可以把Web Service写成一系列的C函数,并仍然使用SOAP进行调用。SOAP规范定义了SOAP消息的格式,以及怎样通过HTTP协议来使用SOAP。SOAP也是基于XML和XSD的,XML是SOAP的数据编码方式。
6.WSDL
你会怎样向别人介绍你的Web Service有什么功能,以及每个函数调用时的参数呢?你可能会自己写一套文档,你甚至可能会口头上告诉需要使用Web Service的人。这些非正式的方法至少都有一个严重的问题:当程序员坐到电脑前,想要使用你的Web Service的时候,他们的工具(如Visual Studio)无法给他们提供任何帮助,因为这些工具根本就不了解你的Web Service。解决方法是:用机器能阅读的方式提供一个正式的描述文档。Web Service描述语言(WSDL)就是这样一个基于XML的语言,用于描述Web Service及其函数、参数和返回值。因为是基于XML的,所以WSDL既是机器可阅读的,又是人可阅读的,这将是一个很大的好处。一些最新的开发工具既能根据你的Web Service生成WSDL文档,又能导入WSDL文档,生成调用相应Web Service的代码。
二、典型的Web Service结构
1.典型的Web Service结构。
不管你的Web Service是用什么工具,什么语言写出来的,只要你用SOAP协议通过HTTP来调用它,总体结构都应如图1所示。通常,你用自己喜欢的语言(如VB.NET)来构建你的Web Service,然后用SOAP Toolkit或者.NET的内建支持来把它暴露给Web客户。于是,任何语言,任何平台上的客户都可以阅读其WSDL文档,以调用这个Web Service。客户根据WSDL描述文档,会生成一个SOAP请求消息。Web Service都是放在Web服务器(如IIS)后面的,客户生成的SOAP请求会被嵌入在一个HTTP POST请求中,发送到Web服务器来。Web服务器再把这些请求转发给Web Service请求处理器。对VB 6.0程序来说,Web Service请求处理器是一个与SOAP Toolkit组件协同工作的ASP页面或ISAPI extension。而对VB.NET程序来说,Web Service请求处理器则是一个.NET Framework自带的ISAPI extension。请求处理器的作用在于,解析收到的SOAP请求,调用Web Service,然后再生成相应的SOAP应答。Web服务器得到SOAP应答后,会再通过HTTP应答的方式把它送回到客户端。((图1))

2.远程过程调用(RPC)与消息传递
Web Service本身实际是在实现应用程序间的通信。我们现在有两种应用程序通信的方法:RPC(远程过程调用)和消息传递。使用RPC的时候,客户端的概念是调用服务器上的远程过程,通常方式为实例化一个远程对象并调用其方法和属性。RPC强调的是远程对象和它的界面,即属性、方法和调用时的参数。DCOM和.NET远程访问都是RPC的例子。
消息传递一般是在耦合度更低的系统中。消息传递的概念是,客户端向服务器发送消息,然后等待服务器的回应。消息传递系统强调的是消息的发送和回应,而不是远程对象的界面。由于是基于消息的系统,客户端和服务器之间的耦合度比RPC方法更低。
RPC系统试图达到一种位置上的透明性:服务器暴露出远程对象的接口,而客户端就好像在使用本地使用的这些对象的接口一样,这样就隐藏了底层的信息,客户端也就根本不需要知道对象是在哪台机器上。例如,你在VB 6.0中通过DCOM调用一个远程对象,你的代码看起来就与调用本地对象一样。而消息传递则不同,它强调传递的东西是什么,但不管消息传递过去后干什么。客户不需要知道服务器是怎么实现的,以及消息是怎么被处理的。
我们已经说过,你可以建立一个消息服务器,根据收到的消息来调用对象。这是通过消息传递方式有效的实现了RPC。如果客户仍然以消息的思维方式来进行操作,那么你可以把它叫做消息传递。但如果客户以远程对象的思维方式来进行操作,那么你就应该把它叫做RPC。
如果你想实现一个基于XML的消息传递系统,大量的工作将集中在处理XML请求和应答消息上。虽然VB 6.0和VB.NET中,帮助你建立Web Service的工具已经做了许多对XML消息进行处理的工作,但毕竟所有的数据都是用XML的形式收发的,许多情况下你还是需要对消息进行一些自己的处理。深入理解XML和XML Schema对于有效地实现XML消息系统是至关重要的。
3.建立Web Service
我知道你现在已经很心急的想要写点代码,看看Web Service到底是什么样的了。那么我们现在就介绍怎样用VB 6.0和VB.NET实际做出一个Web Service来。其目的只是向你展示一下这些工具的功能,而不是深入地讲解Web Service的工作原理。
Microsoft的SOAP Toolkit V2帮助你把COM组件变成Web Service。这套工具分为三大主要部分:SoapClient是一个用于调用Web Service的COM组件;SoapServer 是一个处理SOAP请求和返回SOAP应答的组件;还有一个WSDL向导,它可以把你的type library转换成WSDL文档,以暴露给Web Service的客户。
假设你有一个COM组件,暴露出一个GetTemperature方法:
Public Function GetTemperature(ByVal zipcode As String, _
ByVal celsius As Boolean) As Single
要把这个组件变成一个Web Service,你可以使用WSDL向导。给出你要转换的组件后,向导会要你选择你想暴露出的方法,指出生成的Web Service所在的URL(如http://localhost/Temperature),以及你希望用ASP还是ISAPI做你的请求处理器((图2))。然后向导还会问你生成的WSDL和ASP文件应该放在那个目录下。

现在该调用这个Web Service了。方法是在VB或其他任何可以使用COM的语言里调用SoapClient组件。下面这段代码演示了怎样调用Web Service中的GetTemperature方法:
Dim soap As MSSOAPLib.SoapClient
Set soap = New MSSOAPLib.SoapClient
soap.mssoapinit _
"http://localhost/Temperature/Temperature.wsdl"
MsgBox ("气温是:" & _
soap.GetTemperature("20171", False))
首先调用mssoapinit,把WSDL文档的URL传给SoapClient。WSDL文档的URL就是你在WSDL向导中给出的URL加上〈Service名字.wsdl〉。一旦初始化完成,SoapClient就得到了Web Service的所有方法,你就可以直接调用这些方法了。
三、Web Service实践篇
在这里,我们分两种情况来介绍Web Service,一种是用记事本编写,然后利用wsdl.exe csc.exe来编译;另外一种是借助Microsoft Visual Studio.NET开发工具
1.用记事本编写
你要调用一个Web Service,首先你要知道这个Web Service的wsdl(Web 服务说明语言)地址,这个wsdl是用来描述、说明你所要调用的这个Web Service的,在.NET平台上它是自动生成的,它就可以解析出你提供的服务名称、相关的参数、返回值类型以及服务的URI地址。具备这些信息,客户就可以采用SOAP格式调用你的服务了。
首先我们来发布一个WebService──helloworld.asmx
用记事本编写后保存成helloworld.asmx放在c:\inetpub\wwwroot\test\目录里面:
<%@ WebService Language="C#" Class="test" %>
using System;
using System.Web.Services;
public class test:WebService
{
[WebMethod]
public string helloworld()
{
return "hello,world";
}
}
好了,现在我们通过浏览器来看看我们发布的WebService是什么样子的?
http://localhost/test/helloworld.asmx
看到了没?这就是发布好了的WebService,很简单吧!
接着,我们来看看它的wsdl是什么样子的?
http://localhost/test/helloworld.asmx?wsdl
2.在.NET平台上它是自动生成的
一个Web Service发布好了,我们要怎么去调用呢?你调用的Client端可以是控制台程序,也可以是web的形式...等等。无论是什么形式的Client端,一次Web Service的完整调用是这样的(不包含发现过程)。
(1)客户请求Server的WSDL文档(你的URL应该是WSDL的地址)。
(2)客户解析WSDL的内容,获得服务的名称、地址、参数类型和返回值类型。
(3)根据以上信息,客户可以选择生成一个Proxy类:
用于将调用的参数包装到SOAP Envelop中,生成调用的XML格式数据,发送HTTP请求。接受到HTTP请求的回应之后,解析SOAP数据,生成返回值,并返回。
(4)有了上述Proxy,客户代码就可以透明的调用Web Service了(与调用其他本地函数相同的方式)。
我们先学习怎么用控制台程序去调用这个Web Service吧
第1,2步我们都已经完成了,我们根据wsdl的信息来生成一个代理类(Proxy类):
我们先得到这个Proxy类的源文件吧!
C:\>cd c:\Program files\microsoft.net\frameworksdk\bin
C:\Program Files\Microsoft.NET\FrameworkSDK\Bin>wsdl/l:cs/n:pie
/out:c:\inetpub\wwwroot\test\pie.cs http://localhost/test/helloworld.asmx?wsdl
Microsoft (R) Web Services Description Language Utility
[Microsoft (R) .NET Framework, Version 1.0.2914.16]
Copyright (C) Microsoft Corp. 1998-2001. All rights reserved.
Writing file 'c:\inetpub\wwwroot\test\pie.cs'.
这样我们在c:\inetpub\wwwroot\test\就可以得到代理类pie.cs的源代码。
它是一个以pie为名字空间,里面有个test类,类里面有个接口helloworld()
接下来呢?我们必须把它编译成中间语言*.dll
C:\>cd C:\WINNT\Microsoft.NET\Framework\v1.0.2914
C:\WINNT\Microsoft.NET\Framework\v1.0.2914>csc/t:library /out:c:\inetpub\wwwroot\test\pie.dll/r:System.web.services.dll c:\inetpub\wwwroot\test\pie.cs
Microsoft (R) Visual C# 编译器版本7.00.9254 [CLR version v1.0.2914]
版权所有 (C) Microsoft Corp 2000-2001。保留所有权利。
这样我们就可以在c:\inetpub\wwwroot\test找到代理类的中间语言pie.dll。pie.dll也是我们所需要的,Client端就是通过这个代理来访问,调用WebService的。现在我们可以调用了。
(1)我们先建立调用这个WebService的控制台程序console_pie.cs放在c:\inetpub\wwwroot\test\下
using pie;
using System;
class hehe
{
static void Main()
{
test ll=new test();
string kk=ll.helloworld();
Console.WriteLine(kk);
}
}
我们现在就可以把他编译成可执行文件*.exe
C:\WINNT\Microsoft.NET\Framework\v1.0.2914>csc.exe/r:c:\inetpub\wwwroot\test\pie.dll/out:c:\inetpub\wwwroot\test\console_pie.exe c:\inetpub\wwwroot\test\console_pie.cs
Microsoft (R) Visual C# 编译器版本7.00.9254 [CLR version v1.0.2914]
版权所有 (C) Microsoft Corp 2000-2001。保留所有权利。
现在,整个过程完成了,我们来看看,我们的调用成功了吗?
C:\>cd c:\inetpub\wwwroot\test\
C:\Inetpub\wwwroot\test>console_pie
hello,world
我们可以看到console_pie.exe通过SOAP来调用http://localhost/test/helloworld.asmx成功了。
(2)Client端为Web的形式:由于微软推荐表现与实现的分离,我们也采用这种方式,建立两个文件:web_pie.aspx和web_pie.aspx.cs。
web_pie.aspx的内容:
<%@Pagelanguage="c#"Codebehind="web_pie.aspx.cs"Inherits="web_pie.WebForm1" %>
web_pie.aspx.cs的内容:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
namespace web_pie
{
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Label Label1;
protected System.Web.UI.WebControls.Button Button1;
public WebForm1()
{
Page.Init += new System.EventHandler(Page_Init);
}
private void Page_Load(object sender, System.EventArgs e)
{
}
private void Page_Init(object sender, EventArgs e)
{
InitializeComponent();
}
#region Web Form Designer generated code
private void InitializeComponent()
{ this.Button1.Click += new System.EventHandler(this.Button1_Click);
this.Load += new System.EventHandler(this.Page_Load);
}
#endregion
private void Button1_Click(object sender, System.EventArgs e)
{
pie.test kk=new pie.test ();
Label1.Text =kk.helloworld ();
}
}
}
可以看得出来,web_pie.aspx用于存放网页的表现形式,而web_pie.aspx.cs用于存放网页的实现部分,这样对于以后代码的维护就好多了,而不像以前asp3.0的时候是镶入在html代码中的,不易修改。
使用code behind page的时候(就是独立于.aspx的.aspx.cs文件),需要预先编译.aspx.cs文件为.dll。现在我们来编译:
C:\WINNT\Microsoft.NET\Framework\v1.0.2914>csc/t:library /out:c:\inetpub\wwwroot\test\web_pie.dll /r:c:\inetpub\wwwroot\test\bin\pie.dll c:\inetpub\wwwroot\test\web_pie.aspx.cs
Microsoft (R) Visual C# 编译器版本7.00.9254 [CLR version v1.0.2914]
版权所有 (C) Microsoft Corp 2000-2001。保留所有权利。
编译完成了。
这里说一下,如果Client端是Web端的话,默认是在*.aspx的同一个位置上建立一个bin文件夹,然后把web_pie.dll部署到文件夹里面,这样调用是web会默认自己去bin目录下找名字空间,好了,我们来看一下调用会出现什么样的结果:
http://localhost/test/web_pie.aspx
好了,出现网页了,我们摁下那个button看看
天啊!出错了!找不到文件或程序集名称“pie”,或找不到它的一个依赖项。
通过ildasm.exe查看一下web_pie.dll可以看出他的确没有名字空间pie,我也不明白我编译的时候已经加入/r开关了,为什么没有把pie部署到web_pie名字空间底下!不过无所谓,他没有部署,我们自己来!
把c:\inetpub\wwwroot\test\pie.dll拷贝到bin目录底下!
好了,我们按F5键刷新一下网页,OK,出来了,在Label1上面显示helloworld了
好了,我们讲了Client端的两种形式,其他的形式比如说Windows application Web Service..的形式,举一返三,只要按照前面讲的四个步骤来就行了。
3..NET中的Web Service的开发
.NET平台内建了对Web Service的支持,包括Web Service的构建和使用。与其他开发平台不同,使用.NET平台,你不需要其他的工具或者SDK就可以完成Web Service的开发了。.NET Framework本身就全面支持Web Service,包括服务器端的请求处理器和对客户端发送和接受SOAP消息的支持。这里将带你用.NET创建和使用一个简单的Web Service。
要在.NET中创建Web Service,你只需建立一个.asmx文件。这个文件中有一个WebService标签,包含Language和class两个属性,分别用于指定编程语言和Web Service中暴露出的类。然后你就可以像平常一样编写你的类了。最后,在每个你想要暴露出的方法前面加一个System.Web.Services.WebMethodAttribute属性就可以了。最终代码类似于下面的程序清单。
'a WebService in VB.NET (calc_vb.asmx)
<%@WebService Language="VB"class="Calc" %>
Imports System.Web.Services
Public Class Calc
Public Function Add(ByVal a As Double, _
ByVal b As Double) As Double
Return a + b
End Function
End Class
用浏览器来浏览这个.asmx文件,你可以得到一张测试这个Web Service的页面。例如,你把calc_vb.asmx 文件放到了Web服务器的myService目录下,那么相应的URL就是:
http://localhost/myService/calc_vb.asmx
测试页如(图3)所示。这一页是自动生成的。它显示了Web Service 的名字并列出了可以调用的方法。列表后面有一段文字,意思是你正在使用缺省的命名空间http://tempuri.org,如果你想要发布这个Web Service的话,最好换一个你自己的命名空间,以避免名字上的冲突。

在测试页里单击Add方法,你会得到一个HTML表单,用来测试这个方法((图4))。在这个表单里,Add方法所接受的所有参数都有一个相应的文本框。填好所有的参数,单击"Invoke"按钮,这个表单就会被提交到Web服务器。实际上,这就是通过HTTP GET的形式在调用Web Service。得到的结果是一个如下的简单XML文档:

浏览下面这个URL,可以直接调用Add方法:
http://localhost/myService/calc_vb.asmx/Add?a=123&b=35
如你所见,方法的名字是你所请求的资源(注意,这里是区分大小写的),而函数中的每个参数都映射为查询字符串中的一个参数。这种形式对快速测试一个Web Service 是非常方便的。不过,因为这种方式使用的是HTTP GET,所以它在数据类型和参数传递方向等方面都有一些局限。
回到前面的Web Service测试页,我们还可以看到页面的顶部有一个Service Description链接。单击过去你就可以得到描述这个Web Service的WSDL文档,如(图5)。在Web Service的URL后面加上一个“WSDL”查询字符串,你也可以直接浏览到这一页:

http://localhost/myService/calc_vb.asmx?wsdl
Calc Web Service的WSDL文档。注意,为了显示更多内容,所有的XML元素都已折叠起来。
Calc Web Service的WSDL文档。注意,为了显示更多内容,所有的XML元素都已折叠起来。
要在.NET中调用一个Web Service,你需要先运行wsdl.exe工具。这个工具会从Web Service中读出它的WSDL描述文档,生成一个可以调用这个Web Service的代理类。例如,在命令行中执行下面的命令,可以生成Calc Web Service的代理类:
wsdl.exe /language:VB http://localhost/myService/calc_vb.asmx?wsdl
程序清单1-2截取自生成的VB代理类源码。这个代理类继承自System.Web.Services.Protocols.SoapHttpClientProtocol类,并且暴露出一个Add方法,这个方法接收两个double型浮点数,返回一个double型浮点数。
程序清单1 2 截取自wsdl.exe 生成的Web Service代理类源代码
Imports System.Web.Services.Protocols
'省略其他代码
Public Class Calc
Inherits SoapHttpClientProtocol
'省略其他代码
"http://tempuri.org/Add", _
Use:=System.Web.Services.Description.SoapBindingUse.Literal,_
ParameterStyle:= SoapParameterStyle.Wrapped)>_
Public Function Add(ByVal a As Double, ByValb As Double) As Double
Dim results() As Object = Me.Invoke("Add",_
New Object() {a, b})
Return CType(results(0), Double)
End Function
'省略其他代码
End Class
此后的工作就非常简单了。要调用CalculatorWeb Service,只需实例化一个Calc的代理对象,再调用它的Add方法:
Dim ws As New Calc()
Dim result As Double = ws.Add(20.5, 10.9)
MessageBox.Show("结果是:" &result.ToString)
实际上,代理类中的Add方法仅仅是通过.NET Framework的SoapHttpClientProtocol类来调用Web Service,然后再把Web Service 的返回值返回给调用者。
当然,除了上面的这些演示之外,.NET Web Service的创建和调用还有很多的内容。
提示:在深入这些细节之前,你需要先理解Web Service里面的几个关键技术:XSD、SOAP和WSDL。