使用 Visual Basic® 6.0 的程序员 对 ADO 记录集的推崇向来已久。 它可能是目前在基于 Windows® 的应用程序中使用最为广泛的数据结构。 ADO.NET DataSet 已经准备好在托管的基于 Windows 的应用程序这一领域中扮演类似的角色。
如果需要从数据库中提取信息,显示给用户,并应用更新,DataSet 往往是最佳的选择。 DataSet 已经相当成功地应用于许多针对 Microsoft® .NET Framework 的解决方案。 对于 Web 服务,DataSet 会导致互操作性的问题。
如果您使用类似于 DataSet 的动态数据结构,那么利用其他工具包的开发人员使用您的 Web 服务会更加困难。 但是,通过将类型化 DataSets 与稍微自定义的Web 服务描述语言(WSDL)的定义结合使用可以解决这个问题。 在 Web 服务界面中使用动态数据结构(如 DataSet)之前,开发人员需要理解所有关于动态数据结构的问题。
互操作性的级别
不必知道数据的来源就可以进行处理的能力是最低级别的互操作性。 我喜欢称之为“数据互操作性”。 XML 1.0 经过特别地设计以便于数据的互操作。
使用任何带有 XML 处理器的编程语言(这实际上包含了目前所有的语言),都可以轻而易举地在所有平台上提取和操作按序列进入 XML 1.0 的数据。 实现数据的互操作性仍然需要您为 XML 处理器公开的 XML API 编写代码,但是您不必直接处理字节流(请参见图 1)。
图 1 通过 XML 的数据互操作性
将 XML 应用到分布式应用程序技术(Web 服务)简化了不同分布式系统之间的互操作性。 使用 XML API 可以直接构建 Web 服务。 这种方法使您可以完全控制消息处理过程,但是需要实现大量的公共 Web 服务基础结构,这会降低工作效率并引入可以导致更多互操作性问题的错误。
分布式技术供应商(如 Microsoft)忙于开发提供该公共基础结构的 Web 服务工具包,这有助于减少 Web 服务开发人员需要的 XML API 代码。 Microsoft ASP.NET WebMethod 框架就是这样一个工具包,它几乎删除了所有更低层次 System.Xml API 的痕迹。 Web 服务工具包通过定义应用程序类型和 XML 架构类型之间的映射实现了这个目标,从而在运行时自动处理 XML 文档与对象实例之间的转化。
例如,考虑下面名为 CalculateMortgage 的 WebMethod,它将 MortgageInfo 对象作为输入,并为调用方返回一个 MortgagePayments 对象:
[WebService(Namespace="http://example.org/mortgage")]
public class MortgageService : WebService
{
[WebMethod]
public MortgagePayments CalculateMortgage(
MortgageInfo minfo)
{
... // calculate mortgage
return new MortgagePayments();
}
}
在这个方法中,开发人员只是简单地使用对象,但是 [WebMethod] 属性告诉 ASP.NET 基础结构将该方法视为 Web 服务操作。 因此,在这种情况下基础结构自动将 MortgageInfo 和 MortagePayment 类映射为 XML 架构类型的定义。
|||||| 显示了 CalculateMortgage WebMethod 所使用的 MortgageInfo 和 MortgagePayments C# 类定义,而 显示由 ASP.NET 基础结构生成的 XML 架构类型的定义。 有了这些 XML 架构定义,另一面利用 XML 来表示应该是什么样子的就一目了然了。
Web 服务工具包使得忽略 XML 变得简单易行,因为 Web 服务开发人员只需要处理对象。 然而,在网络另一端的工具包也可能希望将 XML 映射为对象。 例如,使用 Java 语言的开发人员可能会利用类似于 Apache 的 Axis 的工具包使用该 Web 服务。 为此,他会使用 WSDL2Java 实用工具从 XML 架构类型定义自动生成 Java 类。(请参见)。 这使得 Java 开发人员也可以使用对象而不必考虑更低层次的 Java XML API 成为可能。
现在,让我们探讨下一级别的互操作性 - 我称之为“工具包互操作性”。 工具包互操作性的意思是可以在一个工具包中编写 Web 服务代码,并且很容易地由其他工具包使用它而不必考虑更低层次的 XML API。 必须存在等价的对象映射,否则另一方将不得不放弃对象并直接并处理 XML(这在图 5 中进行了说明)。
图 5 工具包互操作性
因为编程语言和类型系统之间存在众多的不同点,工具包互操作性更加难于实现。 DataSet 这种类型给工具包互操作性带来一些有趣的挑战。
DataSet 问题
大多数 Web 服务工具包在映射简单的类定义(如前面提到的定义)时做的很好。 但是,当开始使用派生和替换或者使用动态类型(如集合、哈希表或者 DataSets)时,就会遇到问题了。 您应当考虑以下 .NET Framework 签名:
[WebMethod]
public DataSet GetAuthors()
{
... // return DataSet filled with authors
}
DataSet 应该映射成什么样的 XML 架构? DataSet 是一个多态类型,其真正的结构只有在 DataSet 被填充数据后运行时才能确定。
下面的 XML 架构定义包含 GetAuthorsResponse 的元素声明。 请注意 GetAuthorsResponse 包含一个名为 GetAuthorsResult 的可选元素,该元素包含两个子元素: XMLSchema 架构元素 (s:schema) 的后面紧随着一个通配符 (s:any),该通配符表示可以提供任何命名空间的所有元素:
<s:element name="GetAuthorsResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="0" maxOccurs="1"
name="GetAuthorsResult">
<s:complexType>
<s:sequence>
<s:element ref="s:schema" />
<s:any />
•••
</s:element>
这表明运行时会提供一个架构用来描述跟在架构后面的 XML。 这是在 SOAP 的响应中架构的样子:
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:s="http://www.w3.org/2001/XMLSchema"
>
<soap:Body>
<GetAuthorsResponse
xmlns="http://example.org/dataset-service">
<GetAuthorsResult>
<s:schema> *** schema goes here *** </s:schema>
*** xml goes here ***
•••
</soap:Envelope>
因为工具包只有到运行的时候才能确定 DataSet 中使用的真正类型,下一个问题是在其他环境(如 Apache 的 Axis 工具包)中 XML 架构定义(如上所示)应该映射成什么。
工具包绑定
在设计时,Java 设计人员运行 WSDL2Java,因为架构定义中没有足够的信息,所有能做的只是将 s:any 元素映射为 java.lang.Object,如下所示:
•••
public class GetAuthorsResult implements java.io.Serializable {
private java.lang.Object any;
... // omitted for brevity
}
正如所见到的那样,因为不像对 s:any 前面的 s:schema 元素的引用,Axis 甚至不能再进一步深入;Axis 在这种情况下除了抛出一个异常外,不会产生任何结果。 然而,如果不存在架构引用,Java 类会和前面显示的一样。
因为开发人员和工具包都没有任何其他的类型信息(这里是指有关作者信息的结构),对于工具包来说,唯一有意义的就是在包含已解析 XML 的字段里提供一个 DOM 树。 因此,为了让 Java 开发人员能使用该 Web 服务,不得不放弃它们而直接使用它们对应的 DOM API中的代码告诉您怎样做到这一点。
使用任何在 .NET Framework 中没有 DataSets 具体例子的工具包都会遇到这个问题。 您甚至不得不直接使用 COM 开发人员的 Microsoft SOAP 工具包来处理 DOM API。
按照先前提供的定义,在这种情况下(即 XML)仍然存在数据互操作性,但是因为要求使用的开发人员处理原始 XML,就失去工具包互操作性。 如果我知道会返回一个很好定义的作者记录集合,我可以使用几种方法让其他工具包的用户更容易地处理我的数据而不用直接接触 XML API。
规避 DataSet
在这个问题上最简单的办法可能就是在 Web 服务界面中不使用 DataSets。 例如,在这种情况下您可以定义一个创建作者信息的类,并且按照下面的方式声明 GetAuthors 方法:
public class Author
{
public string id;
public string au_fname;
public string au_lname;
•••
}
[WebMethod]
public Author[] GetAuthors()
{
DataSet authorsDS = GetAuthorsFromDB();
... // walk through DataSet and generate
// Author array to return
}