本文假定你对Visual StudioNET和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对象的实例(参见示例)
这种方法不是很好因为你设计的这些方法和对象仅对你这个Web服务有意义使用这个Web服务的程序对你设计的CurrentWeather类却一无所知例如类成员所表示的具体意义事实上客户程序仅仅知道它接收了一个名叫CurrentWeather的XML架构(XSD)复杂类型当你访问Web服务的WSDL说明(形如weatherserviceasmx?wsdl)时会自动的创建这一XSD类型的定义客户端工具就会根据自身的情况将这一XSD类型映射成为数据结构例如SOAP工具会把CurrentWeather实例映射成为IXMLDOMNodeList接口的XML节点NET Framework通常会把这个XSD类型转换成为本地类型从而带来很多麻烦例如增加了对示例中Web服务引用应用会根据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消息示例就体现了示例的请求和返回消息可以作为你设计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类)然后就成为你工程里的一个新的类型随后你就可以对这些类声明变量了就像示例中的cw变量
XML架构设计器使声明新的类型变得容易通过在CurrentWeather元素的界面内增添行的方式就可以定义新的CurrentWeather类型就如示例展示的那样CurrentWeather元素包含有未命名的类型这个类型包含有ConditionsIconUrlHumidityBarometerFahrenheitTemperature和CelsiusTemperature元素
示例Visual Studio XML架构设计器
到此为止就还有最后一步来完成消息的设计你要为XML架构设置targetNamespace属性来唯一的标识你的Web服务或应用程序就像在示例种属性窗口中的设置一样这是一个良好的习惯但它不是必须的以致于很多开发者都忽略了这重要的一步
如过你需要对XML架构进行深入的工作你可以通过点击示例中底部的XML附签来切换到XML视图它展现了整个XML架构的源文件你可以根据需要对它进行深入的编辑就像示例那样
示例消息架构
完成界面的定义
示例中的XML架构充分描述了请求和返回消息的数据定义但消息还有很多其他方面的信息需要被用一种正式的机器可读的方式所描述出来例如消息是否使用了SOAP远程调用?消息是怎样被编码的?为了提供这些方面的信息你可以创建Web服务描述语言(WSDL)文件现在Visual Studio NET里还没有内置的WSDL设计器不过有第三方的产品可以提供图形用户界面的WSDL设计器而且WSDL是符合XML语法的你可以用任何的XML或者是文本编辑器来创建WSDL文件由于需要提供大量的特性所以WSDL语法会有一些复杂但实际上你只会用到其中很小的一部份你最好建立一个WSDL模板文件(类似示例中的那样)然后在其基础上修改来满足你的需要
示例展示了为天气服务定制的WSDL模板文件我修改了请求与返回消息中的部分信息指向在ServiceMessages架构中设计的WeatherRequest和CurrentWeather元素还把操作命名为GetWeather把绑定命名为WeatherInterface这些命名都会在你的代码中分别作为Web方法名称和绑定名称所以尽量使名称有良好含义
到此为止就完成了设计的工作你现在准备好了Web服务界面的正式设计包括每个请求和返回消息中的应用程序数据的定义接下来服务的开发者就应该开始实现这些接口的功能客户端的开发者也要进行相应的编程了清注意客户端的开发者不需要等到服务开发完成后才能开始开发工作这意味着你可以通过让两种开发同时进行来节省时间
接口的实现
你可以使用Microsoft® NET Framework SDK中的wsdlexe工具来实现一个Web服务的接口这一命令行工具可以对WSDL文件进行操作生成Web服务的stub类(实现了给定接口的类)或代理类(客户端使用的类)
例如想要生成天气服务接口的stub类你可以运行wsdlexe后面加/server开关传递给它接口的WSDL地址如下面这样
C:\> wsdlexe /server /o:WeatherStubcs Microsoft (R) Web Services Description Language Utility[Microsoft (R) NET Framework Version ]Copyright (C) Microsoft Corporation All rights reservedWriting file WeatherStubcs
默认的wsdlexe会生成C#代码如果你可以使用/language开关来选择生成Visual Basic® NET或JavaScript语言在这个例子中生成的WeatherStubcs文件包含一个抽象的WeatherInterface类来实现天气的接口这个类包含一个GetWeather方法它可以接受一个字符串参数然后返回一个CurrentWeather的实例CurrentWeather类是根据在XML架构中的CurrentWeather类型而生成的
开始实现服务代码的时候把WeatherInterface类加入到你的工程中创建一个新的Web服务然后把这个Web服务改成从WeatherInterface抽象类继承然后你就可以重写基类的GetWeather方法实现你的业务逻辑就像示例那样为了通过SOAP展现这个GetWeather方法你需要加入一个WebMethod属性来明确这个GetWeather方法就是较早前在WeatherInterfacewsdl定义的那个GetWeather操作的具体实现你还需要在返回的信息中加入一个Xml元素添加一个SoapDocumentMethod属性并且把它的ParameterStyle属性设置成为SoapParameterStyleBare(请参见示例)
如果你使用Visual Basic NET开发你不用手动的添加这些属性当你从overrides菜单中选择GetWeather 方法时Visual Studio就会自动地把它们添加在你的代码里
如果你编写了Web服务并且找到那个自动生成的WSDL文件你会发现里面包含了很多的信息这确实是个麻烦首先WSDL文件里面包含完整的WeatherInterface其次文件还包含了支持HTTP GET和HTTP POST协议的另外两个接口
包含有完整的接口会影响单一接口多次实现的目标所以你首先要从服务的WSDL文件中删除那些接口的内容然后确保它指向你先前创建的WeatherInterfacewsdl文件想达到这一目的你需要给Web服务本身增加一个WebServiceBinding属性指出服务实现的WSDL绑定(WeatherInterface绑定)的名称以及定义绑定的WSDL文件的地址你还需要设定每个Web方法的SoapDocumentMethod属性的绑定属性指出这些Web方法在绑定中定义的具体操作示例展示了经过这些修改的Web服务
最后我建议你关闭服务的WSDL文件中绑定的HTTP GET和HTTP POST如果是为某些特定的应用程序关闭这些协议你只要编辑应用的nfig文件在systemweb节中增加webServices节即可如下所示
<webServices> <protocols> <remove name=HttpGet/> <remove name=HttpPost/> </protocols></webServices>
你也可以通过编辑nfig文件来对整个计算机关闭以上的协议从协议列表中删除HttpGet和HttpPost即可
现在当你创建了Web服务并且查看其WSDL文件时你得到了一个简洁的文件仅仅引入了WeatherInterfacewsdl文件并且添加了服务定义(参见示例)你可以把服务定义也看成是WeatherInterfacewsdl中定义接口的特殊实现
对界面编程
开发客户端程序来实现天气接口你可以使用wsdlexe或者是在Visual Studio里添加Web引用来从接口生成代理类注意当你生成这个类时你需要指出WeatherInterfacewsdl因为里面包含着接口信息特别当类中没有实现这些接口的服务的地址时因此客户端必须在设计时和运行时都设置这一地址避免在客户端代码中写死这个地址你最好把它放置在你客户端的配置文件中在运行时动态读取就可以了以下就是WeatherInterface的客户端代码
Private Sub btnWeather_Click( _ ByVal sender As SystemObject _ ByVal e As SystemEventArgs) Handles btnWeatherClick Dim ws As New localhostWeatherInterface() wsUrl = SystemConfiguration _ ConfigurationSettingsAppSettings(ServiceUrl) Dim cw As localhostCurrentWeather = _ wsGetWeather()End Sub
总结
Web服务是利用XML消息来传递数据的技术所以编写Web服务的时候尤其要精心的用XML架构和WSDL来设计消息的结构当你从设计消息结构开始而不是从编写方法开始的时候你的Web服务需要接收和返回何种类型的数据就变得比较清晰了使用XSD和WSDL来设计消息结构你可以创建一个标准的接口定义让Web服务的开发者去实现同时客户端的开发者也可以参照其来进行编程下次你再开发Web服务的时候请先用Visual Studio XML架构设计器来设计消息的结构吧