本文假定你对Visual Studio.NET和xml技术都比较熟悉
摘要
Web服务是利用xml消息来传递数据的技术。如果你要设计一个数据库结构,你大概不会让你的工具去自动的完成,而是要手工的进行调整来保证最高的效率,作者在这篇文章中认为,设计Web服务也是一样的。你需要首先了解Web服务会返回什么样的数据,然后再用这样的数据结构来设计出最有效率的消息格式。你将从这篇文章中学习到如何决定有效的消息结构,并且根据这一结构来创建相关的Web服务。
没有经验的开发者在开发Web服务时,经常跳过设计这一重要的第一步,他们从给工程添加Web引用开始,然后再给服务中添加相应的Web方法。虽然这种方法比较简便,却不是创建真正用于业务的Web服务的好办法,因为这样的话就忽略了Web服务消息结构的设计。在这篇文章中,我会向大家解释为什么这种常规的办法不适合重要的开发,然后会教给大家用Visual Studio® .NET设计Web服务的更好办法。
当编写面向数据的应用时,如何去创建数据库结构呢?你是不是会先设计出类程序然后再让你的集成开发环境帮你自动创建数据库结构,还是你会亲自手动创建标准化的,完整的和高效的数据库结构?一般情况下你都会选择手动的来创建数据库结构。即使你是利用了可视化的数据结构设计器,而不是使用数据定义语言(DDL)来进行设计,你依然对数据结构的设计有良好的控制。
Web服务的目标就是在恰当的时候提供恰当的数据。当客户端调用Web服务时,xml格式的消息被用于发送请求和返回结果。当编写Web服务和相应的客户端程序时,你主要就是针对这些消息格式编程,应用程序真正关心的就是这些消息中承载的数据。既然如此,为什么要先设计出Web服务的类和方法,然后再用工具自动的生成消息的数据结构?你应该先设计出消息的数据结构,然后再根据这一结构来设计Web服务,就像你在设计数据库结构时一样。
优先考虑消息是一个进步,例如,某个Web服务接收地区编码,然后返回当地的天气情况,习惯了面向对象的方法,你会自然而然的就设计出GetWeather方法来获取字符参数,然后返回一个CurrentWeather对象的实例。(参见示例1)
这种方法不是很好,因为你设计的这些方法和对象仅对你这个Web服务有意义,使用这个Web服务的程序对你设计的CurrentWeather类却一无所知,例如类成员所表示的具体意义。事实上,客户程序仅仅知道它接收了一个名叫CurrentWeather的xml架构(XSD)复杂类型,当你访问Web服务的WSDL说明(形如weatherservice.asmx?wsdl)时会自动的创建这一XSD类型的定义,客户端工具就会根据自身的情况将这一XSD类型映射成为数据结构。例如,SOAP工具会把CurrentWeather实例映射成为IxmlDOMNodeList接口的xml节点。.NET Framework通常会把这个XSD类型转换成为本地类型,从而带来很多麻烦,例如,增加了对示例1中Web服务引用的.net应用会根据CurrentWeather的XSD类型自动生成一个名叫CurrentWeather的类:
public class CurrentWeather { public string Conditions; public string IconUrl; public Single Humidity; public Single Barometer; public float FahrenheitTemperature; }
对那些不知道这一Web服务功能的人来说,第一个问题就是“CelsiusTemperature属性哪里去了,为什么FahrenheitTemperature是一个域而不是属性”?答案就是,只有服务内CurrentWeather类的公共,可读写的成员会被.NET Framework自动序列化成为xml,因为CelsiusTemperature是只读的,它就没有被序列化,也就不能在客户端的CurrentWeather类中体现出来,而且,自动生成的客户端类只包含公共域,而不是属性。客户端程序仅仅知道有名叫CurrentWeather的XSD类型,但XSD类型并没有区分属性和域,对于客户端来讲都只是数据。
从这个角度看,你会觉得Web服务有着很严重的局限性,不能够让客户端得到服务所返回对象的实例。这其实并不是Web服务的局限,而只是你不能把Web服务看成是获取远程对象(就像DCOM一样)的手段的原因。
设计天气服务的更好办法是从定义消息开始。例如,你定义了WeatherRequest消息,包含有地区编码和包含有当地天气情况的CurrentWeather消息。示例2就体现了示例的请求和返回消息,可以作为你设计Web服务的起点。
现在,由于Web服务和客户端的开发者都有可以理解这一Web服务的WeatherRequest消息和CurrentWeather消息,他们就不再会被一些实现细节所困惑了,例如只读成员和属性等。
现在你已经创建了样例的请求和返回消息,你需要把这些消息的设计格式化,以便Visual Studio .NET等开发工具可以理解这些设计,并且提供更加强大的开发功能,如客户代理生成。
你需要去创建一个xml架构来描绘你的请求和返回消息,从而格式化这些消息的设计。虽然你可以用很多种的工具甚至文本编辑器来生成这些xml架构文件,我还是建议你使用Visual Studio的xml架构编辑器。
使用xml架构编辑器,启动Visual Studio并且选择菜单: 新建 | 文件 | xml架构。设计器有一个专门的工具箱面板,里面包含有xml架构的组件,例如元素声明和类型定义等,想声明WeatherRequest一类的元素,只需要把元素(Element)图标从工具箱中拖到设计界面上就可以了。每个元素都包含有两个必须设置的主要属性:元素的名称和数据类型,当你设置元素的数据类型时,你可以从XSD内置的类型中选择,也可以自定义类型。例如,WeatherRequest就是内置的字符串类型(WeatherRequest元素包含字符串),而CurrentWeather则是包含有其他元素的自定义类型。
对类型进行定义,然后再声明这些类型的具体元素,这和你在编程时所作的工作非常类似:先定义类(例如CurrentWeather类),然后就成为你工程里的一个新的类型。随后你就可以对这些类声明变量了,就像示例1中的cw变量。
xml架构设计器使声明新的类型变得容易,通过在CurrentWeather元素的界面内增添行的方式就可以定义新的CurrentWeather类型。就如示例3展示的那样,CurrentWeather元素包含有未命名的类型,这个类型包含有Conditions,IconUrl,Humidity,Barometer,FahrenheitTemperature和CelsiusTemperature元素。
到此为止就还有最后一步来完成消息的设计,你要为xml架构设置targetNamespace属性,来唯一的标识你的Web服务或应用程序,就像在示例3种属性窗口中的设置一样。这是一个良好的习惯,但它不是必须的,以致于很多开发者都忽略了这重要的一步。
如过你需要对xml架构进行深入的工作,你可以通过点击示例3中底部的xml附签,来切换到xml视图。它展现了整个xml架构的源文件,你可以根据需要对它进行深入的编辑,就像示例4那样。
示例4:消息架构
示例4中的xml架构充分描述了请求和返回消息的数据定义,但消息还有很多其他方面的信息需要被用一种正式的,机器可读的方式所描述出来。例如消息是否使用了SOAP远程调用?消息是怎样被编码的?为了提供这些方面的信息,你可以创建Web服务描述语言(WSDL)文件。现在Visual Studio .NET里还没有内置的WSDL设计器,不过有第三方的产品可以提供图形用户界面的WSDL设计器,而且WSDL是符合xml语法的,你可以用任何的xml或者是文本编辑器来创建WSDL文件。由于需要提供大量的特性,所以WSDL语法会有一些复杂,但实际上你只会用到其中很小的一部份,你最好建立一个WSDL模板文件(类似示例5中的那样),然后在其基础上修改,来满足你的需要。
示例6展示了为天气服务定制的WSDL模板文件,我修改了请求与返回消息中的部分信息,指向在ServiceMessages架构中设计的WeatherRequest和CurrentWeather元素,还把操作命名为GetWeather,把绑定命名为WeatherInterface。这些命名都会在你的代码中分别作为Web方法名称和绑定名称,所以尽量使名称有良好含义。
到此为止就完成了设计的工作,你现在准备好了Web服务界面的正式设计,包括每个请求和返回消息中的应用程序数据的定义。接下来服务的开发者就应该开始实现这些接口的功能,客户端的开发者也要进行相应的编程了。清注意,客户端的开发者不需要等到服务开发完成后才能开始开发工作,这意味着你可以通过让两种开发同时进行来节省时间。
你可以使用Microsoft® .NET Framework SDK中的wsdl.exe工具来实现一个Web服务的接口。这一命令行工具可以对WSDL文件进行操作,生成Web服务的stub类(实现了给定接口的类)或代理类(客户端使用的类)。
例如,想要生成天气服务接口的stub类,你可以运行wsdl.exe,后面加/server开关,传递给它接口的WSDL地址,如下面这样:
C:> wsdl.exe /server /o:WeatherStub.cs http://localhost/MSDNMag/weatherinterface.wsdl Microsoft (R) Web Services Description Language Utility [Microsoft (R) .NET Framework, Version 1.0.3705.0] Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. Writing file 'WeatherStub.cs'.
默认的,wsdl.exe会生成C#代码,如果你可以使用/language开关来选择生成Visual Basic® .NET或JavaScript语言。在这个例子中,生成的WeatherStub.cs文件包含一个抽象的WeatherInterface类来实现天气的接口。这个类包含一个GetWeather方法,它可以接受一个字符串参数,然后返回一个CurrentWeather的实例。CurrentWeather类是根据在xml架构中的CurrentWeather类型而生成的。
开始实现服务代码的时候,把WeatherInterface类加入到你的工程中,创建一个新的Web服务,然后把这个Web服务改成从WeatherInterface抽象类继承,然后你就可以重写基类的GetWeather方法,实现你的业务逻辑,就像示例7那样。为了通过SOAP展现这个GetWeather方法,你需要加入一个WebMethod属性,来明确这个GetWeather方法就是较早前在WeatherInterface.wsdl定义的那个GetWeather操作的具体实现。你还需要在返回的信息中加入一个xml元素,添加一个SoapDocumentMethod属性,并且把它的ParameterStyle属性设置成为SoapParameterStyle.Bare。(请参见示例7)
如果你使用Visual Basic .NET开发,你不用手动的添加这些属性,当你从overrides菜单中选择GetWeather 方法时,Visual Studio就会自动地把它们添加在你的代码里。
如果你编写了Web服务,并且找到那个自动生成的WSDL文件,你会发现里面包含了很多的信息,这确实是个麻烦。首先,WSDL文件里面包含完整的WeatherInterface,其次,文件还包含了支持HTTP GET和HTTP POST协议的另外两个接口。
包含有完整的接口会影响单一接口多次实现的目标。所以你首先要从服务的WSDL文件中删除那些接口的内容,然后确保它指向你先前创建的WeatherInterface.wsdl文件。想达到这一目的,你需要给Web服务本身增加一个WebServiceBinding属性,指出服务实现的WSDL绑定(WeatherInterface绑定)的名称,以及定义绑定的WSDL文件的地址。你还需要设定每个Web方法的SoapDocumentMethod属性的绑定属性,指出这些Web方法在绑定中定义的具体操作。示例8展示了经过这些修改的Web服务。
最后,我建议你关闭服务的WSDL文件中绑定的HTTP GET和HTTP POST。如果是为某些特定的应用程序关闭这些协议,你只要编辑应用的Web.config文件,在system.web节中增加webServices节即可,如下所示:
<webServices> <protocols> <remove name="HttpGet"/> <remove name="HttpPost"/> </protocols> </webServices>
你也可以通过编辑machine.config文件来对整个计算机关闭以上的协议,从协议列表中删除HttpGet和HttpPost即可。
现在,当你创建了Web服务并且查看其WSDL文件时,你得到了一个简洁的文件,仅仅引入了WeatherInterface.wsdl文件,并且添加了服务定义(参见示例9),你可以把服务定义也看成是WeatherInterface.wsdl中定义接口的特殊实现。
开发客户端程序来实现天气接口,你可以使用wsdl.exe,或者是在Visual Studio里添加Web引用来从接口生成代理类。注意,当你生成这个类时,你需要指出WeatherInterface.wsdl,因为里面包含着接口信息,特别当类中没有实现这些接口的服务的地址时。因此,客户端必须在设计时和运行时都设置这一地址。避免在客户端代码中写死这个地址,你最好把它放置在你客户端的配置文件中,在运行时动态读取就可以了。以下就是WeatherInterface的客户端代码:
Private Sub btnWeather_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnWeather.Click Dim ws As New localhost.WeatherInterface() ws.Url = System.Configuration. _ ConfigurationSettings.AppSettings("ServiceUrl") Dim cw As localhost.CurrentWeather = _ ws.GetWeather("20171") End Sub
Web服务是利用xml消息来传递数据的技术,所以编写Web服务的时候尤其要精心的用xml架构和WSDL来设计消息的结构。当你从设计消息结构开始,而不是从编写方法开始的时候,你的Web服务需要接收和返回何种类型的数据就变得比较清晰了。使用XSD和WSDL来设计消息结构,你可以创建一个标准的接口定义,让Web服务的开发者去实现,同时客户端的开发者也可以参照其来进行编程。下次你再开发Web服务的时候,请先用Visual Studio xml架构设计器来设计消息的结构吧。