您的位置:首页 > 其它

使用WSDL组合多个Web服务以增加应用程序的使用范围

2006-08-29 09:57 597 查看
编者注:WSDL,Web Services Description Language,Web服务描述语言。WSDL 是一种XML格式,用于将网络服务描述为一组端点,这些端点对包含面向文档信息或面向过程信息的消息进行操作。这种格式首先对操作和消息进行抽象描述,然后将其绑定到具体的网络协议和消息格式上以定义端点。相关的具体端点即组合成为抽象端点(服务)。可以对WSDL进行扩展,这样无论通信时使用何种消息格式或网络协议,都可以对端点及其消息进行描述。

企业解决方案通常聚合了来自无数个内部应用程序和外部源的信息。作为一种容易且可靠地使用这些解决方案所需各种数据的方法,Web服务(Web Service)已得到了迅速采用。但不可避免会出现一些特殊的情况,即单个解决方案要求使用多个互补的Web服务,而这些服务通常共享特定于公司或特定于域的XML类型。遗憾的是,正是那些帮助推动了广泛采用Web服务的工具以及它们所提供的强大抽象,可能经常妨碍开发人员窥探组成Web服务栈的XML标准的内幕。本文将提供一个解决方案,以便实现为互补的Web服务创建的代理之间的类型共享,同时提供一个分析Web服务描述语言(WSDL,Web Services Description Language)及其与我们了解并喜欢的Web服务工具之间的交互的机会。
当我第一次尝试创建两个互补的Web服务以共享常见的XML类型时,我遭遇到一个令我不太愉快的意外。在我的客户端应用程序中创建的每个代理,都在不同的C#命名空间中具有常见消息的不同对象表示。我必须将每个属性从一个类的实例手动复制到另一个类的实例,而这似乎是这些工具当前实现的一项不必要的结果。最后,我无意中发现了一种技术,从而能够公开单个Web服务协定,该协定通过创建带有多个绑定的WSDL文件封装了多个Web服务终结点。这就显式地定义了服务之间的关系,而且还具有代理的优势,这些代理可以共享表示常见消息的托管类。在完成上述工作之后,可以通过手动编辑工具生成的代理,在相同命名空间中共享由多个协定定义的类型。但是,通过将Web服务绑定组合到单个WSDL文件中,Web服务实现者可以定义共享关系,而不是给为服务创建客户端的开发人员增加负担。该技术还使您每次在开发过程中更改服务协定时,无需重新创建对代理类进行的更改。
XML命名空间 VS CLR命名空间
在继续讨论以前,有必要确保您理解命名空间的概念,并且理解XML上下文中的命名空间和托管代码上下文中的命名空间之间的区别。这两种类型的命名空间都具有类似的目的:帮助开发人员避免名称冲突。它们使一组项可以包含多个具有相同名称但含义不同的项。它使您可以使用名称“Order”来同时表示客户的订单及其购物车中某个项的顺序。那样,命名空间就像用来区分“John Smith”和“John Brown”的姓氏。让我们通过分析代码段1中显示的XML命名空间来考察这一点。
代码段1 XML命名空间:

<CustomerInfo
xmlns:o="http://www.example.com/OrderService"
xmlns:s="http://www.example.com/ShoppingCartService "
>
<o:Orders>
<o:Order orderNumber="1">
<!-- other order information would go here -->
</o:Order>
</o:Orders>
<s:ShoppingCart>
<s:Item>
<s:Order>1</s:Order>
</s:Item>
</s:ShoppingCart>
</CustomerInfo>

在代码段1中,架构(由使用 http://www.example.com/OrderService 命名空间的公司创建)中的Order元素可以与架构(由使用 http://www.example.com/ShoppingCartService 命名空间的公司创建)中的 Order元素和睦相处。用作XML命名空间的URI通常但不总是采取URL的形式。但是,并没有任何要求(并且情况通常不是这样)规定这些URL必须实际指向某个架构文件或其他任何实际资源。
为了防止XML文件令人难以忍受地冗长(这通常是对XML格式的批评之一),可以使用命名空间前缀作为定义命名空间的实际URI引用的简写形式。当该命名空间前缀被追加到命名空间中元素的本地名称时,就可以借助于分号得到该元素的限定名称。下面是一个示例:

+ ":" + =
"o" + ":" + "Order" = "o:Order"

类似地,托管世界中的命名空间可以将类与完全相同的名称区分开来。这方面的一个很好的示例是:Windows窗体库包含一个名为System.Windows.Forms.Button的类,而ASP.NET库包含一个名为System.Web.UI.WebControls.Button的类——这不会出现任何问题。尽管我的示例使用了不同程序集中的类,但您需要记住的是,在单个程序集中也可以具有多个带有相同名称的类——只要它们位于不同的命名空间中。
什么是绑定
在我深入探讨该解决方案之前,让我们花一点儿时间来快速回顾一下构成WSDL文件的活动部分。我将重点讨论一个通过HTTP操作的非常简单的document-literal Web服务。存在很多个选项(例如,SoapHeaders和不同的传输以及各种编码),因为WSDL期望成为一种具有高度可扩展性的标准。简单的WSDL文件所描述的服务,其正文包含一个自定义XML文档、document部分(在该服务中使用XML架构定义语言[XSDL,XML Schema Definition Language] 定义)以及literal部分。如果您需要有关RPC/encoded Web服务、WSDL、SOAP或其他构成Web服务栈的XML标准的更详细信息,则请访问 MSDN Web Services Developer Center。同时,我还将向您简要介绍一下服务协定。
WSDL是一种用于描述由SOAP信封传送的消息的有线格式的标准。WSDL文件是一个充当Web服务协定的XML文档。它可以由该Web服务的使用者用作指南,以便创建和验证与该服务之间往来传送的XML有效负载。让我们观察一下WSDL文件的主干(如代码段2所示),以便更好地了解它的组件。请记住,这不是有效的服务协定。我已经通过移除某些属性和命名空间声明,针对该示例简化了它,以便我可以集中讨论最有趣的部分。
代码段2 简化的WSDL文件:

<wsdl:definitions
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:my="http://example.com/supersimplewebservice"
targetNamespace="http://example.com/supersimplewebservice"
>
<wsdl:types>
<xsd:schema>
<xsd:element name="RequestMessageElement"
type="xsd:string" />
<xsd:element name="ResponseMessageElement"
type="xsd:string" />
</xsd:schema>
</wsdl:types>
<wsdl:message name="RequestMessage">
<part name="RequestMessagePart"
element="my:RequestMessageElement" />
</wsdl:message>
<wsdl:message name="ResponseMessage">
<part name="ResponseMessagePart"
element="my:ResponseMessageElement" />
</wsdl:message>
<wsdl:portType name="PortTypeName">
<wsdl:operation name="GetWeather">
<wsdl:input message="my:RequestMessage" />
<wsdl:output message="my:ResponseMessage" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="MyServiceBindingSoap">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"
style="document" />
<wsdl:operation name="GetWeather">
<soap:operation soapAction="" style="document" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="SuperSimpleService">
<port name="StockQuotePort" binding="tns:StockQuoteBinding">
<soap:address
location="http://example.com/supersimplewebservice.asmx"/>
</port>
<wsdl:service>
</wsdl:definitions>

定义元素是WSDL文件的根元素,并且包含我将分析的其余元素。作为根元素,它还包含WSDL命名空间、SOAP命名空间、XSD命名空间以及任何自定义命名空间(由WSDL文件或被导入的架构中定义的元素所使用)的命名空间声明。在示例文件中,http://example.com/supersimplewebservice 命名空间用“my”命名空间前缀声明。我将分析的下一个元素是types,它类似于内联的XSD。它定义了XML元素的结构,XML元素构成了由WSDL文件描述的消息(在这种情况下,为RequestMessageElement和ResponseMessageElement)。WSDL设计者还可以选择在外部架构文件中定义类型;可以使用import将该外部架构文件导入到服务协定中(示例文件中未显示)。我将导入的架构用于突出消息的协定优先设计,您将在进一步观察该解决方案时了解这一点。
下面应该讨论消息元素了。每个消息元素都描述一个将由WSDL文件中定义的绑定使用的独特消息。消息元素主要充当部件元素的容器。每个部件都具有一个元素属性,该属性指定了基础XSD类型(例如,string、float)或在WSDL文件的types节中定义的元素之一。尽管可以在单个消息内部包含多个部件元素,但您将遇到的大多数WSDL文件都将只包含单个部件。portType元素充当一组操作以及在每个操作期间发送或接收的消息的容器。可以将操作视为已定义的消息交换,并将其建模为操作元素的实例。如果您从面向对象编程的角度考虑,您还可以看到在操作和抽象方法签名之间存在不太明显的相同之处。操作元素可以具有一个或零个输入、输出或错误元素。如果您曾经观察过WSDL文件,则您可能已经见到过指定操作所接受的向内和向外绑定消息的元素,但是我尚未见到过自然状态下的错误元素。尽管操作的每个子元素都是可选的,但必须至少定义一个输入或输出消息。实际上,大多数Web服务都至少包含一个输入消息。这些子元素中的每一个元素都包含一个消息属性,该属性指定了在该文件前面部分中定义的消息元素之一的名称。
现在,让我们考察一下binding元素——更准确地说是wsdl:binding元素。我先前已经讨论过的所有元素都已经以抽象的方式定义了一些消息,这些消息可能会也可能不会与消息在网络中传送时的表示方式直接对应。Binding通过详细说明消息的实际格式、在交互期间使用的协议以及用于交换消息的网络传输机制,定义了消息的具体格式。Binding元素的type属性指定了该元素要链接到的端口类型。它的operation子元素指定了该binding所对应的端口类型内部的特定操作,以及要使用的输入、输出或错误消息。每个binding还都包含可扩展性元素,它们用来指定特定于该binding所使用的协议的其他信息。由于我的示例服务使用SOAP 1.1作为它的协议,因此它包含soap:binding子元素。请记住,这两个binding元素具有相同的本地名称,但实际上是不同的元素,因为它们位于不同的命名空间中。示例服务中的soap:binding元素的transport属性指定该服务将使用HTTP而不是SMTP或TCP/IP作为它的网络传输。soap:binding元素的style属性将wsdl:binding中指定的操作所使用的默认正文样式声明为document或rpc(在该示例中,为document)。示例WSDL文件中的另一个binding可扩展性元素为soap:operation 元素,它指定了实现该binding的服务所期望的SOAPAction标头。它还提供了重写soap:binding元素所指定的默认样式的能力。
我们要考察的最后一个元素是service。每个service元素都对应于一个特定的 Web服务终结点,并且定义了该终结点公开的名称和端口集。service元素还包含一个可扩展性元素,在该示例中,该元素为soap:address。soap:address元素的location属性指定了终结点本身的URI。当您开始生成真正的WSDL文件时,您可能会注意到它们不包含service元素,因为它们准备供多个终结点重新使用。
解决方案的设计
到目前为止,您已经具有足够的基础知识来考察我先前描述的技术。Yasser Shohoud在他的文章“Place XML Message Design Ahead of Schema Planning to Improve Web Service Interoperability”(参见 MSDN?Magazine 2002 年12月刊)中介绍了Web服务的突出消息的协定优先设计。在这篇文章中,Yasser向您演示了创建具有以下功能的Web服务的步骤:使用消息优先的设计方法返回有关当前天气的信息。概括说来,他首先定义了将用其服务交换的XML消息。然后,他使用该XML结构定义了一个XML架构,并且定义了一个抽象服务说明。该抽象服务说明用来创建该服务的实现。
通过使用突出消息的协定优先设计,Web服务设计者可以定义将在网络中传送的消息的确切结构,而不是设计和实现对象表示并且让工具来决定网络级表示。可能存在很多已经开发的Web服务——它们的设计师从未考虑过他们的服务的消息在网络中呈现的状况,这通常是因为这些服务是在单个平台上实现和测试的,并且只通过代理使用。如果强迫这些开发人员深入到其开发工具所提供的抽象之下来解决难解的问题,或者如果他们需要利用XML开发人员可以使用的强大工具,则他们可能会突然感到非常吃惊,因为他们面对的是其消息不够理想的XML表示。
假设您是一个不同开发团队的一分子,要求该开发团队生成一个新的名为CalculateChanceOfRain的Web服务,该服务将作为Yasser的团队所实现的GetWeather服务的补充。新的Web服务将使用一个在原始服务架构中定义的元素,但是它将返回一个浮点值以表示基于当前天气的降水概率。您的服务将驻留在它自己的位于不同数据中心的服务器上,并且将使用与GetWeather服务不同的后端系统。实际上,您可以轻松地假设新服务是由完全独立的合作伙伴组织开发的。您无法访问原始服务的源代码或其他实现详细信息,而只能访问架构和WSDL文件。图1显示在与这两个服务的单用户交互中的操作序列。



图1 单用户事务概述
如您所见,从客户端的观点来看,这两个服务实现构成了一个“虚拟的”聚合服务。如果您承担设计新解决方案的任务,则您首先想到的可能是为新服务创建一个全新的服务协定(WSDL文件),以便导入为GetWeather服务定义的架构。遗憾的是,使用该技术将意味着当前的.NET工具会将这两个Web服务视为完全不同的实体。当使用Visual Studio .NET或WSDL.exe来为这两个Web服务创建代理时,每个代理都将具有它自己的针对“CurrentWeather”消息的对象表示。这些类将完全相同,并且将被序列化和反序列化为完全相同的XML结构。实际上,您可以使用XmlSerializer将一个类的实例序列化为文档,然后成功地将同一文档反序列化为另一个类的实例。但是,从运行库的角度来看,这两个类是不同的命名空间中恰好具有相同名称的不同托管类。既然如此,那么从对GetWeather代理对象的方法的调用中返回的对象实例就不能传递给CalculateChanceOfRain代理的方法。它们在托管世界中是不同的类,就像System.Windows.Forms.Button和System.Web.UI.WebControls.Button是不同的类一样。这意味着您只能在不同类的实例之间复制值,或者手动编辑由工具生成的代理代码。如果消息不是非常巨大的话,那么在字段之间复制数据可能不会造成多么大的性能差异,但这一定是我宁愿避免的一项额外的维护任务。如果您决定手动更改代理,则您必须移除两个表示常见XML消息的类之一,并且对那个被移除表示的代理重新进行编码,以使用另一个代理的类。请记住,每当您创建对服务的新Web引用时,都必须重新实现这些更改。在我看来,这些解决方案都不是特别理想,因为它们使客户端开发人员必须在服务之间实施消息的概念性共享,从而增加了他们的负担。
通过创建带有针对每个实现服务的binding的单个接口WSDL,您可以获得某种级别的封装,以显式定义两个服务实现之间至今存在的隐式关系。与只是具有一个GetWeather服务和一个CalculateChanceOfRain服务不同,您可以公开一个包含这两组行为的虚拟WeatherServices服务。您还可以让您使用的Web服务工具创建这两个服务共有的数据类型的单个类表示。让我们考察一下如何创建包含这两个服务的binding的聚合服务说明。
创建WeatherServices WSDL
创建新聚合服务说明的第一步是确定将要传递给新的CalculateChanceOfRain服务的消息。正如我先前已经讨论过的那样,作为输入传递给新Web服务的消息将是由Yasser的GetWeather服务返回的相同CurrentWeather元素。让我们观察一下原文中的示例XML:

<CurrentWeather xmlns="http://learnxmlws.com/Weather">
<Conditions>Sunny</Conditions>
<IconUrl>http://www.LearnXmlws.com/images/sunny.gif</IconUrl>
<Humidity>0.41</Humidity>
<Barometer>30.18</Barometer>
<FahrenheitTemperature>75</FahrenheitTemperature>
<CelsiusTemperature>23.89</CelsiusTemperature>
</CurrentWeather>

如您所见,CurrentWeather元素包含一些子元素,其中包含当前情况的文本说明、摄氏温度和华氏温度,以及有关特定地区天气的其他信息片段。新的CalculateChanceOfRain服务表面上将使用这一有关天气的信息来预测该地区的降水概率。降水的百分比概率将以包含浮点值的简单元素的形式返回给调用方。您将需要创建一个新的架构以定义该输出消息——您将其称为CalculateChanceOfRainResponse。新的RainServiceMessages架构的内容如下所示:

<xs:schema
xmlns:mstns="http://somecompanydotcom/RainServices"
xmlns="http://somecompanydotcom/RainServices"
targetNamespace="http://somecompanydotcom/RainServices"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
>
<xs:element name="CalculateChanceOfRainResponse" type="xs:float" />
</xs:schema>

既然已经定义了消息,那么您就可以创建新的聚合服务接口。与从头创建新的WSDL文件不同,您将修改由负责GetWeather服务的团队创建的WSDL文件。代码段3(参见光盘)显示该文件在其当前格式下的内容。
要创建新的聚合服务接口,需要将WeatherInterface.wsdl的内容复制到一个新的文件中(我将该文件称为Aggregate_WeatherInterface.wsdl)。然后,您需要将新的RainServiceMessages架构的命名空间添加到新WSDL文件中的定义元素中。接下来,添加一个使用CurrentWeather元素作为其数据部件的请求消息,以及一个使用新的CalculateChanceOfRainResponse元素的响应消息。您还需要为新的服务向WSDL文件中添加一个port和一个binding。代码段4(参见光盘)显示已完成的Aggregate_WeatherInterface服务说明。在完成之后,您需要立即将WSDL文件移动到客户端可以访问的位置。当您告诉客户端开发人员有关Web服务的信息时,或者当您使用像UDDI这样的服务发布该服务时,您可以将用户引导到该静态WSDL文件的位置,而不是使用在查询字符串中用“?WSDL”访问ASMX URL而创建的动态生成文件。当然,就像所有经验丰富的Web服务开发人员一样,一旦您进入实际工作环境,您总是将该动态文件保存为静态文件。如果您使用反射来动态生成它,则它就不是协定。您还可以使用 Building XML Web Services Using Industry Standardized WSDLs中描述的机制,让动态生成的WSDL指向静态的WSDL。
实现CalculateChanceOfRain服务
既然聚合服务说明已经完成,那么您现在就可以着手实现CalculateChanceOfRain Web服务了。该过程中的第一个步骤是使用wsdl.exe工具为服务实现创建一个抽象存根,就像Yasser在实现GetWeather服务时所做的那样。以下是我用来创建 CalculateChanceOfRain 存根的命令行:

wsdl /server /o:RainServiceStub.cs
http://localhost/WeatherService/aggregate_weatherinterface.wsdl

如果您分析一下所生成的Web服务存根文件,则您会注意到该文件包含两个抽象的Web服务类。每个类都对应于聚合Web服务说明文件中包含的binding之一。因为您只是使用该类实现您的CalculateChanceOfRain Web服务,所以您应当手动从该存根文件中移除GetWeather binding实现;但是,这只是为了更加美观一些,而没有任何真正的技术动机。您将需要保持CalculateWeather类的定义,因为在您的方案中,您无法访问用于定义GetWeather服务项目中类的源代码。代码段5(参见光盘)显示修改过的Web服务存根文件。
现在,您需要创建从Web服务存根类中派生的服务实现。第一个步骤是创建一个新的Web服务项目。本文随附的示例代码中的项目名为“RainService”。向该项目中添加一个名为MyRainService的Web服务,然后打开它的代码隐藏文件。代码段6显示MyRainService Web服务代码隐藏文件在修改之后所包含的代码。
代码段6 完成的MyRainService Code:

[WebService(Namespace = "http://somecompanydotcom/RainServices")]
[WebServiceBinding(Name="CalculateChanceOfRainInterface",
Namespace="http://learnxmlws.com/Weather",
Location="http://localhost/WeatherService/
aggregate_weatherinterface.wsdl")]
public class MyRainService : CalculateChanceOfRainInterface
{
public MyRainService()
{
// This call is required by the ASP.NET Web Services Designer
InitializeComponent();
}
[WebMethod]
[SoapDocumentMethod("", ParameterStyle=SoapParameterStyle.Bare)]
[return: XmlElement ("CalculateChanceOfRainResponse",
Namespace="http://somecompanydotcom/RainServices")]
public override float CalculateChanceOfRain(
[System.Xml.Serialization.XmlElementAttribute(
Namespace="http://learnxmlws.com/Weather")]
CurrentWeather CurrentWeather)
{
float result = 0;
if (CurrentWeather.Conditions == "Rainy") result = 100;
else
{
Random rnd = new Random(DateTime.Now.Millisecond);
result = (float)rnd.Next(101);
}
return result;
}
}

请注意,我已经向该类应用了WebServiceAttribute,以便设置将由该服务使用的命名空间。这并非严格需要,因为ASP.NET Web服务基础结构将默认使用tempuri.org命名空间,但是对于生产Web服务而言,这是一个好主意。我还向该类应用了WebServiceBindingAttribute类,以指示该Web服务的binding在先前创建的聚合服务说明中定义。该属性至关重要;它指定该服务将使用聚合WSDL文件中定义的CalculateChanceOfRainInterface binding。由于我对气象学一无所知,对于风暴等气候现象也缺乏了解,所以CalculateChanceOfRain方法的实现只是返回一个介于零和一百之间的随机浮点值。更准确的天气预报逻辑作为练习留待读者来完成。鉴于此,该算法的成功率很可能能够与我在电视上看到的很多本地天气预报员的成功率相媲美。
实现GetWeather服务
由于您的情况是虚构的,因此您需要创建GetWeather服务的示例实现,以便测试您的解决方案。相关步骤几乎与您在实现上一个Web服务时所遵循的步骤完全相同,因此我不再对它们详加讨论。唯一需要说明的是,您将再次使用WSDL.EXE工具生成Web服务存根,然后创建一个新的类文件以保持服务实现。代码段7(参见光盘)显示Web服务的完整实现。正如我已经说过的那样,我对气象学几乎一无所知,并且我没有天气检测设备。myGetWeather方法将忽略由调用方指定的邮政编码,而不是与天气信息提供商签订合同。该服务返回一个填充了随机值的CurrentWeather实例。由于CalculateChanceOfRain服务无论如何都会忽略发送给它的消息,因此这一点并不重要。
WeatherServices客户端应用程序
到目前为止,您已经准备好测试新的聚合Web服务。为此,请创建一个简单的名为WeatherClient的Windows窗体应用程序,该应用程序将充当Web服务的客户端。它看起来如图1中的窗体(不带背景图像)。
现在,让我们考察一下该实现需要的内容。首先,需要向主窗体中添加一些控件,以使用户可以与该应用程序交互。向主窗体中添加一个文本框,并将其命名为“Zipcode”,然后向该窗体中添加一个按钮,并将其命名为“SubmitButton”。最后,添加一个标签以显示计算结果,并将其命名为“DisplayLabel”。接下来,使用Visual Studio .NET添加一个对聚合服务说明的引用。请记住,应该将该引用添加到所创建的静态WSDL文件,而不是ASMX 件的动态创建的WSDL。聚合服务说明不包含用于指定服务实现的终结点的service元素,因此您在调用Web服务之前必须手动设置代理实例的URL属性。
像很多.NET XML Web服务开发人员一样,我在添加Web引用之后完成的第一件事情是将Web引用的URL Behavior属性设置为Dynamic。在设置该属性以后,代理类通过分析应用程序配置文件中的设置来确定Web服务终结点的URL,因此您不需要将该URL硬编码到类本身中。遗憾的是,对于您已经创建的多重绑定文件,该方法不起作用,因此您必须手动创建配置项,并且设置代理实例的URL属性。
现在,您已经做好了向应用程序中添加客户端代码的准备。代码段8显示该窗体中的重要代码节。
代码段8 Client Code:

private void SubmitButton_Click(object sender, System.EventArgs e)
{
//let the user know we're working
this.Cursor = Cursors.WaitCursor;
updateStatus("Getting weather!", false);
//call the GetWeather method asynchronously
weatherservices.WeatherInterface weatherService =
new WeatherClient.weatherservices.WeatherInterface();
weatherService.Url =
ConfigurationSettings.AppSettings["weatherServiceUrl"];
weatherService.BeginGetWeather(ZipCode.Text,
new AsyncCallback(this.getWeatherResult), weatherService);
}

private void getWeatherResult(IAsyncResult ar)
{
//we're returned from our call to GetWeather!
updateStatus("Getting chance of rain!", false);
//let's get the result of our call to GetWeather
weatherservices.WeatherInterface weatherService =
(weatherservices.WeatherInterface)ar.AsyncState;
weatherservices.CurrentWeather currentWeather =
weatherService.EndGetWeather(ar);
//now we need to call the GetWeather method asynchronously
weatherservices.CalculateChanceOfRainInterface rainCalculator =
new WeatherClient.weatherservices.CalculateChanceOfRainInterface();
rainCalculator.Url =
ConfigurationSettings.AppSettings["rainServiceUrl"];
rainCalculator.BeginCalculateChanceOfRain(currentWeather,
new AsyncCallback(this.getCalculateChanceOfRainResult),
rainCalculator);
}

private void getCalculateChanceOfRainResult(IAsyncResult ar)
{
weatherservices.CalculateChanceOfRainInterface rainCalculator =
(weatherservices.CalculateChanceOfRainInterface)ar.AsyncState;
float chanceOfRain = rainCalculator.EndCalculateChanceOfRain(ar);
string resultMessage =
string.Format("There is a {0}% chance of rain.", chanceOfRain);
updateStatus(resultMessage, true);
}

// a delegate that is used by updateStatus to invoke itself recursively
private delegate void UpdateStatusDelegate(
string textToDisplay, bool changeCursorToDefault);

private void updateStatus(string text, bool changeCursorToDefault)
{
if (DisplayLabel.InvokeRequired)
{
DisplayLabel.Invoke(new UpdateStatusDelegate(this.updateStatus),
new object[] { text, changeCursorToDefault});
return;
}

DisplayLabel.Visible = true;
DisplayLabel.Text = text;
if (changeCursorToDefault) this.Cursor = Cursors.Default;
}

在按钮的Click事件的事件处理程序中,使用Web服务代理类启动对GetWeather 服务的调用。为了使用户界面能够快速响应,使用Web服务代理对Asynchronous Method Invocation Design Pattern的实现。这会使Web服务请求在ThreadPool中的线程上执行,而不是在需要处理已发布的Windows消息的用户界面线程上执行,以便使UI能够快速响应。getWeatherResult方法被指定为对GetWeather方法的异步调用的AsyncCallback委托。代理是第三个参数。当GetWeather Web服务调用完成时,将执行getWeatherResult方法。getWeatherResult方法将作为其参数提供的IAsyncResult对象的AsyncState 属性转换为CurrentWeather的实例。然后,它使用CurrentWeather实例作为对 CalculateChanceOfRain Web服务的调用的参数。getCalculateChanceOfRainResult方法被指定为对GetWeather方法的异步调用的AsyncCallback委托。当执行getCalculateChanceOfRainResult方法时,调用updateStatus方法,以便通过更新DisplayLabel标签的Text属性来向用户显示降水概率。正如您可能知道的那样,Windows窗体控件不是线程安全的,并且它们的方法只应当从用户界面线程中调用。当在ThreadPool辅助线程上收到来自Web服务的异步响应时,DisplayLabel的InvokeRequired属性将为true。该方法随后通过DisplayLabel.Invoke递归调用它自己,以便在用户界面线程上分配该标签的Text属性。
.NET Framework 2.0中的类型共享
类型共享功能在即将发布的Microsoft .NET Framework 2.0中得到了极大的改善,知道这一点一定会令您感到非常高兴。下一个版本的WSDL.EXE工具通过添加sharetypes命令行参数,使得服务之间的类型共享变得更为简单。通过向该工具提供多个WSDL URL以及向参数中添加sharetypes标志,您可以告诉该工具,您希望在生成的代理之间共享任何公共类型。公共类型是指在同一架构文件中定义的具有相同本地名称和命名空间的元素。以下是一个示例命令行,它可以用来生成Web服务客户端存根,以便用来调用两个共享类型的Web服务:

wsdl /sharetypes http://localhost/service1.wsdl http://localhost/service2.wsdl

所创建的文件将包含与每个Web服务的binding相对应的类,以及与在服务之间共享的每个类型相对应的单个类。实际上,所创建的文件几乎与使用.NET Framework 1.x的开发人员在添加对聚合WSDL文件的引用时所创建的文件完全相同。显然,Web服务团队注意到了一直在强烈要求这一新功能的Web服务开发社区的呼声。
如果您正在测试 .NET Framework Beta 1,则您可能会注意到,在开发环境中当前未公开该新功能。请尽管放心,在以后的测试版中,将从Visual Studio 2005开始提供同一功能。
如果您正在考虑使用带有多个binding 的WSDL文件,并且您的服务也使用SoapHeaders,则您需要小心。在这一特定方案中,客户端代理生成存在一个已知的问题,即只有第一个binding的SoapHeaderAttribute参数才是正确的。
小结
显然,这里完成的封装不够严密。客户端应用程序非常清楚存在两个不同的binding,因为存在两个不同的代理类。它们还清楚地知道,两个服务在不同的位置活动,因为客户端负责配置用来访问这些服务的终结点的URL。同时,也没有必要告诉客户端应用程序这两个服务是由不同团队在不同时间单独实现的。您还可以在客户端代码中共享这两个Web服务之间的类型,从而避免了很多将一个结构中的数据复制到不同命名空间中另一个完全相同结构的工作。
由于使用.NET Framework中提供的工具来创建和使用XML Web服务相当容易,因此很多开发人员和设计师从来没有花费时间来熟悉Web服务栈的基础协议。确实,再也没有比编写标准方法、向其应用WebMethodAttribute并且专门使用自动生成的代理对象更容易的事情了。但是,对基础协议以及工具使用这些协议的方式进行一定的了解,可以扩充您在设计解决方案时的可用选择。衷心希望本文能够激励您抽出一些时间来探索有关的秘密,即使您已经集中精力使用了.NET Framework 2.0中的sharetypes功能。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: