http://www.microsoft.com/china/msdn/Archives/msdnonline/features/articles/soapguide_ado.asp
用 SOAP 消息传递返回 ADO 记录集
Chris Dengler
Microsoft 开发人员网络
2000年7月
摘要:本文提供了用 SOAP 引出 Web Service 中现有的 COM 对象的准则。本文通过研究返回 ADO 记录集的两种不同方案说明了这些准则,最后讨论了每种解决方案的优缺点,以及在对数据进行打包和检索时可能出现的其它问题。
单击此处可找到相关文档,并可下载 SOAP Toolkit for Visual Studio 6.0 。(英文)
目录
Web Service 概念的引入带来了一种全新的、强有力的方法,用于在 Internet 上进行方法调用。开发人员现在可以利用“简单对象访问协议”(SOAP) 技术,使用户远程调用 Internet Information Services (IIS) 服务器上现有 COM 对象的方法。
因为这种新的调用进程是基于消息的,所以它在语言和平台方面是未知的。意思是说开发人员可用他们最钟爱的语言来开发客户机代码和服务器对象。Microsoft® SOAP Toolkit 使开发人员能够在 Microsoft Windows® 2000 服务器上使用这项技术快速而容易地在 Web 上引出方法。
在将 Microsoft SOAP Toolkit 添加到新的开发工具库以后,我想测试的第一项内容就是,SOAP 与我现有的 Web 应用程序结合得怎么样,以及有多么容易。许多现有的 COM 对象和 Web Service 都通过 Microsoft ActiveX® Data Objects (ADO) 记录集将信息返回到客户机。我开发的用来跟踪我的音乐库的 Web Service 就属于这种情况。
一个 SQL Server™ 数据库包含了我关于艺术家和歌曲的记录。因为 SOAP Toolkit 能够帮助在 Internet 上引出我的服务(并且几年前就出现了在 ASP 页面或简单客户机应用程序中访问此功能的技术),所以我认为,通过 SOAP 将我的音乐库服务引出是一件相当容易完成的事情。
在安装了 SOAP Toolkit 并将它的组件注册以后(有关正确安装必需的 SOAP 文件的信息,请参阅 SOAP Toolkit 下载包中的 SOAP Toolkit 文档),我就准备通过 SOAP 引出我的第一个 COM 对象。首先运行 SOAP Toolkit Wizard,并使它指向我的列表中的第一个 COM 对象 — 该对象返回我的音乐库中音乐家的一个列表(以及其它一些有帮助的方法)。该向导返回下面的屏幕(图 1)。
图 1. SOAP Toolkit Wizard
我最感兴趣的方法用红色加以高亮显示!发生了什么事?
此处显而易见的问题实际上根本就不成为问题。它只要求对 SOAP 到底是什么,以及它如何调用方法并返回数据有一定的了解。有了这种了解,您就可以完成某些相当强大的编程目标。
请注意位于方法右侧的“注”区域中明确显示:
“用红色高亮显示的方法要么返回未知的数据类型,要么包含未知的参数变量类型。如果您一定要选择它,则会在 SDL 文件中将有问题的类型定为数据类型 'string'。”
回想一下,我记得这些高亮显示的方法返回的是 ADO 记录集。因此,我要么必须为该对象编写一段包装代码,要么必须重写我的 COM 对象。
深入研究 SOAP 的工作方式证实,SOAP 的意图就是在对分布式对象技术没有任何共享理解的情况下传递消息。ADO 记录集对象是特定于 Microsoft COM 技术的。因为 SOAP 的一个主要目标就是平台和技术之间的互操作性,所以我们的返回 SOAP 程序包不可能返回 ADO 记录集这样的对象。
为了说明这种基于消息的技术,让我们进一步考察实际的 SOAP 程序包到底返回什么。下面的 SOAP 程序包返回值取自 SOAP Toolkit 的 Microsoft Visual Basic® 样例,该样例调用方法 GetServerTime:
正如我们在上面的样例代码节选中看到的那样,SOAP 的返回值是基于 XML 文本的消息。不可能通过这种范例返回像 ADO 记录集这样的对象。另一方面,过去我已将 ADO 记录集转换为 XML;那么采用什么办法来改变这种情况呢?实际上,我可以将 ADO 记录集转换为 XML,并通过 SOAP 将它传递给我的客户机!简单的返回值可能类似以下代码:
<artists> <artist>ABBA</artist> <artist>Aeromsmith</artist> <artist>Alice Cooper</artist> <artist>Bad Company</artist> <artist>Barry White</artist> <artist>Brash</artist> ...end so on </artists>
这样我们就发现了一种方法,使用 SOAP 和一点小创新就可以返回某个对象的数据,如 ADO 记录集。在下一节,我们将探索不同的方法来完成这一任务。
通过选择方法 GetAllArtists 并选择使用 ASP 解决方案,我将生成我的服务所必需的源文件。在下一节中讨论各种可能的解决方案时,将会用到这些文件。
注: SOAP Toolkit 文档更详细地说明了使用 ASP 解决方案与使用 ISAPI 解决方案之间的差异。
将记录集打包
虽然下面的每个解决方案都为我的问题提供了解决方案,但每个方案都各有其优缺点,我将在下面对此进行详细说明。
我们已经用 SOAP Toolkit Wizard 创建了源文件。现在我们将看一看它所生成的 ASP 接口文件(因为我们选择了使用 ASP 监听程序解决方案)。之所以选择使用 ASP 解决方案,是因为它具有这样的灵活性,即它允许在 COM 对象完成的处理之前和之后添加其它代码。
注: 相比之下,ISAPI 监听程序解决方案最适合调用不需要其它代码处理的 COM 对象的情况,而我们现在需要进行这种处理。虽然 ISAPI 监听程序不像 ASP 监听程序那样灵活,但它能够提供比 ASP 监听程序更好的性能。另外,如果我们能够修改现有的 COM 对象来返回 XML,或者编写另一个 COM 对象来包装现有对象的功能,并使该包装对象返回 XML,则 ISAPI 解决方案可能会是更好的选择。
向导生成的函数如下所示:
Public Function GetAllArtists () Dim objGetAllArtists Set objGetAllArtists = Server.CreateObject("Radioweb.clsSongs") GetAllArtists = objGetAllArtists.GetAllArtists() '在此处插入其它代码 Set objGetAllArtists = NOTHING End Function
请记住,虽然 GetAllArtists 将返回一个 ADO 记录集,但是因为这并不是一种符合 SOAP 的数据类型,所以 SOAP Toolkit Wizard 会指定缺省的返回数据类型 "string"。因此,为了返回我们的数据,该字符串返回值将必须是记录集的一种 XML 表示。
现在我将概要介绍两种不同的解决方案。解决方案 1 实现了一个单独的函数,将记录集转换为一种 XML 表示。解决方案 2 则使用 ADO 记录集的 Save 方法,通过 ADO Stream 对象将数据保持为 XML 格式。
本文的后半部分会说明每种解决方案的优缺点。
对于第一种解决方案,我们将实现“粘合代码”,以方便从记录集到 XML 字符串的转换,如下所示:
Public Function GetAllArtists () Dim objGetAllArtists Set objGetAllArtists = Server.CreateObject("Radioweb.clsSongs") GetAllArtists = GetXMLFromADORS(objGetAllArtists.GetAllArtists()) '在此处插入其它代码 Set objGetAllArtists = NOTHING End Function
其中 GetXMLFromADORS 如下所示:
Public Function GetXMLFromADORS(ByVal objADORS) Dim objField Dim strXMLString Dim strFieldName Dim strFieldValue strXMLString = "<RECORDSET>" & vbCrLf Do while not objADORS.EOF strXMLString = strXMLString & vbTab & "<RECORD>" & vbCrLf For each objField in objADORS.Fields strFieldName = objField.name strFieldValue = objADORS(strFieldName) If instr(1, strFieldValue, "/") Or _ instr(1, strFieldValue, ">") Or _ instr(1, strFieldValue, "<") Then strFieldValue = CDATAit(strFieldValue) End If strXMLString = strXMLString & vbTab & vbTab & _ "<" & strFieldName & ">" & strFieldValue & "</" & _ strFieldName & ">" & vbCrLf Next objADORS.MoveNext strXMLString = strXMLString & vbTab & "</RECORD>" & vbCrLf Loop strXMLString = strXMLString & "</RECORDSET>" & vbCrLf GetXMLFromADORS = strXMLString End Function
GetXMLFromADORS 函数将不会像 ADO Save 方法(将在解决方案 2 打包中讨论)那样强健,但它不要求在客户机上安装 Microsoft Data Access Components (MDAC)。其原因是,客户机只须分析简单的 XML 结构,而不必将 XML 转换为 ADO 记录集。
我还实现了 CDATAit 函数,它将防止插入可能形成畸形 XML 的字符串。在调用过程内部可根据需要使用此逻辑,GetXMLFromADORS 函数的情况也是这样。在上面的示例中,我只对 ">"、"<" 和 "/" 进行了检查。
Private Function CDATAit(ByVal strData) CDATAit = "<![CDATA[" & strData & "]]>" End Function
GetAllArtists 函数现在将返回类似下面的结果:
GetAllArtists 函数现在返回了形式良好的 XML 记录集。请注意,我们的 XML 中不存在任何 CDATA 区域。如果任何艺术家的名字包含了我们所查找的字符("/"、"<" 或 ">"),CDATAit 函数就会将它们包装在 CDATA 区域,以确保形成形式良好的 XML。
我们必须进行的唯一额外更改就在向导所生成的“服务描述语言”(SDL) 文件中。该文件是用来描述我们正在引出的 Web Service 的 XML 文件。它将位于您指示向导将您的文件保存在其下的同一文件夹中。必须修改向导为我们的返回数据生成的数据类型 — 以便“远程对象代理引擎”(ROPE)能够按 XML 正确分析数据,需要指明数据类型是一种 XML 方案。请注意,客户机代码仍将把返回数据视为由某种 XML 结构表示的字符串变量。
在这种情况下,将生成如下所示的 SDL 文件:
将需要修改描述数据返回值的部分。您将注意到,这在我们的 SDL 文件中是这样描述的:
<element name='GetAllArtistsResponse'> <type> <element name='return' type='dt:string'/> </type> </element>
我们的返回值不再是由 SDL 所描述的字符串,而是表示记录集的一个结构。因此,对此文件的修改需要将此返回值表示为一个结构,而不是一个字符串。可以通过将该文件更改为以下形式来完成这一工作:
现在请注意,我们是将 'return' 元素作为一种 'MyReturnStruct' 类型返回,而不是作为一个字符串返回。
'MyReturnStruct' 是我们将要定义的一种结构元素。它将是我们返回的结构的父结构。请记住,该结构必须具有如下的形式:
<RECORDSET> <RECORD> <artist>Some artist</artist> </RECORD> .. .. .. </RECORDSET>
因此,'MyReturnStruct' 是一种定义如下的元素:
<element name='MyReturnStruct'> <type> <element name='RECORD' type='ArtistStruct'/> </type> </element>
请注意,'MyReturnStruct' 具有一个名为 'RECORD' 的元素,其类型进一步定义为 'ArtistStruct'。这是必需的,因为子元素 <artist> 实际上是 <RECORD> 的一个子代,而后者又是 <RECORDSET> 的一个子代。
然后将 'ArtistStruct' 定义为如下形式:
<element name='ArtistStruct'> <type> <element name='artist' type='dt:string'/> </type> </element>
其中,数据类型为 'string' 的元素 'artist' 代表结构中的单个艺术家。
在研究用来分析这些数据的方法之前,让我们考察一下能够获得相同结果的另一种方法。
将记录集转换为形式良好的 XML 字符串的另一种方法是,使用 ADO 将记录集保存到作为目的地的一个 ADO Stream 对象中,并保持为 XML。
注: MSDN Magazine 就这一主题有一篇更为深入的文章,"使用 ADO 来创建基于 XML 的记录集",作者为 Don Box (http://msdn.microsoft.com/library/periodic/period00/com0700.htm)(英文)。Hanson Brothers 还实现了一个深入的示例,请参阅 http://msdn.microsoft.com/xml/articles/hbinterop_core.asp(英文)。
按这种方式返回记录集的粘合代码稍微容易一点。我已在 ASP 接口文件中实现了以下函数:
Public Function GetXMLStreamFromADORS(ByVal objADORS) Dim oStream 'As ADODB.Stream Set oStream = Server.CreateObject("ADODB.Stream") objADORS.Save oStream, adPersistXML GetXMLStreamFromADORS = CDATAit(oStream.ReadText) Set oStream = Nothing End Function
我已在此 ASP 文件的开头定义了以下常数:
CONST adPersistADTG = 0 CONST adPersistXML = 1
只包括了这两个常数,因为它们是仅会用到的两个常数。如果您选择了 Microsoft Visual Studio® 6.0,则您也可以使用它附带的 adovbs.inc 文件。
现在,应该通过修改函数 GetAllArtists() 的代码,使其实现新的帮助器函数 GetXMLStreamFromADORS,如下所示:
Public Function GetAllArtists() Dim objGetAllArtists Set objGetAllArtists = Server.CreateObject("Radioweb.clsSongs") GetAllArtists = GetXMLStreamFromADORS(objGetAllArtists.GetAllArtists()) 'Insert additional code here Set objGetAllArtists = NOTHING End Function
之所以必须添加 CDATAit 函数(在解决方案 1 打包中说明),是因为必须将此 XML 字符串表示为一个字符串,以便通过 ROPE 正确地将其返回。我们也可以像在解决方案 1 中那样修改 SDL,但因为这种结构更难于解释和预测,所以它可能非常令人厌烦并容易出错。通过使用 CDATAit 函数,我们可以用数据类型 'string' 在 CDATA 区域返回该结构。此外,我们还可以在将来的记录集实现中使用这些函数,而且不必修改 SDL 文件。
该函数实际上通过 ADO Stream 对象为我们返回记录集的 XML 表示,如下所示:
我们随后将按字符串检索数据,就像在我们的 SDL 中定义的那样。请记住,我们的 SDL 是按如下方式来定义返回值的:
<element name='GetAllArtistsResponse'> <type> <element name='return' type='dt:string'/> </type> </element>
因为我们的数据实际上包装在 CDATA 区域中,所以可以保证,当从 Stream 对象返回时,我们的数据是合法的 XML。
注: 这些示例中所使用的函数,如 GetXMLStreamFromADORS 和 GetXMLFromADORS,可以很容易地放入一个 include 文件中,实现类似 COM 对象(返回 ADO 记录集)的 ASP 接口文件都可包括该文件。在您的 Web Service 的整个生存期中,这种可重用性都将有助于今后的开发。
检索记录集
既然已成功地将返回数据打包,那么必须在客户机应用程序中正确分析这些数据。根据您决定使用哪一种上述解决方案,可以有几种不同的方法来实现这一点。我们将单独考察每个解决方案。
解决方案 1 检索
解决方案 1 中返回的数据是记录集的一种简单 XML 数据表示。现在,我们将使用 Microsoft XML Document Object Model (DOM) 来检索此数据,以便在内部循环处理每一条记录,XML 对这一过程的表示如下:
Private Sub Command1_Click() Dim aryArtists() As String Dim oRope as New Rope.Proxy oRope.LoadServicesDescription icURI, "http://MyServer/GetArtists.xml" GetArtistsFromXML oRope.GetAllArtists(), aryArtists() '现在 aryArtists() 是包含我们的艺术家的一个数组 Set oRope = Nothing End Sub Private Sub GetArtistsFromXML(ByVal sXML As String, ByRef aryArtists() As String) Dim oXML As New MSXML.DOMDocument Dim oXMLDocument As MSXML.IXMLDOMElement Dim iCount As Integer Dim x As Integer oXML.loadXML sXML Set oXMLDocument = oXML.documentElement iCount = oXMLDocument.childNodes.length ReDim aryArtists(iCount - 1) For x = 0 To iCount - 1 aryArtists(x) = oXMLDocument.childNodes.Item(x).Text Next Set oXML = Nothing Set oXMLDocument = Nothing End Sub
Command1_Click() 方法使用 ROPE 调用服务。因为该服务按解决方案 1 中指定的方式返回数据,所以现在就可以传递包含在我们的 XML 字符串中的返回数据。我们按值传递该字符串,并按引用传递一个将被置入 Public Sub GetArtistsFromXML 中的空数组。此过程置入数组并将它返回给调用过程。数组变量 aryArtists() 现在包含我们的艺术家的一个数组。
解决方案 2 检索
为了将此 XML 数据返回给一个 ADO 记录集,我们需要将数据加载到 XML DOM 中,然后使用 ADO 记录集的 OPEN 方法创建艺术家的记录集:
Private Sub Command1_Click() Dim oRS As ADODB.Recordset Dim oRope as New Rope.Proxy oRope.LoadServicesDescription icURI, "http://MyServer/GetArtists.xml" Set oRS = CreateADORSFromXML (oRope.GetAllArtists()) '现在 oRS 是包含我们的艺术家数据的一个分离的 ADO 记录集 Set oRope = Nothing Set oRS = Nothing End Sub Private Function CreateADORSFromXML(ByVal sXML As String) As ADODB.Recordset Dim oXML As New MSXML.DOMDocument Dim oRS As New ADODB.Recordset oXML.loadXML sXML oRS.Open oXML Set CreateADORSFromXML = oRS Set oRS = Nothing Set oXML = Nothing End Function
在 Command1_Click() 事件中,ROPE 对象调用我们的方法,该方法会就像在解决方案 2 中那样返回我们的 XML 字符串。通过调用 CreateADORSFromXML 函数,就可以设置一个对象引用从 SOAP 返回数据创建一个分离的记录集。
虽然每个解决方案都为我们的问题提供了答案,但有时您可能想使用的是某一种方案,而不是另一种。下面的两个表提到了几个示例。
表 1. 解决方案 1 的优缺点
优点 | 缺点 |
因为不使用 ADO,所以不要求在客户机上安装 MDAC。因此,开发人员不必将 MDAC 打包到应用程序的发行包中。 | GetXMLFromADORS 函数是用来处理简单记录集的。更复杂的记录集结构可能要求使用 ADO 为您的解决方案重新创建 XML。用您的数据结构测试此函数,以确保能够正确表示您的记录集。 |
GetXMLFromADORS 函数必须分析每个字段中的文本来构建 XML 表示。对于大型记录集,这可能是一个时间较长的过程,因为该函数要查找可能导致畸形的 XML 的字符,以便“净化”文本。 |
表 2. 解决方案 2 的优缺点
优点 | 缺点 |
将 COM 对象返回的原始记录集从 XML 重新构建成一个 ADO 记录集对象。这样,开发人员就可以针对 ADO 记录集编写代码,就好象它是原始对象一样。 | 必须在客户机上安装 MDAC。这将增加您的安装程序和分发文件的大小。 |
与 GetXMLFromADORS 函数相比,能够更准确地表示 XML 记录集的 ADO 持久性。 | |
数据的真正面对对象的表示。 |
一种解决方案可能比另一种工作得更好,这取决于您的需要。如果您无法控制您的客户机是否安装 MDAC,则您可能要选择解决方案 1 的方法。如果数据的真正的对象表示是必需的,而且已经知道客户机上安装了 MDAC,则应该选择解决方案 2。
当实现这些类型的转换时,这两种解决方案都可能会产生一些问题。这些问题既适用于数据打包,也适用于数据检索。还可能出现其它问题,这取决于数据的完整性、结构和大小等等。
数据字段
当使用解决方案 1 时,数据字段不能包含可能导致畸形 XML 的字符(<、>、/,等等)。函数 GetXMLFromADORS 使用逻辑
"<" & strFieldName & ">"
这可能导致畸形的 XML 表示。像 <、> 和 / 这样的字符,可能导致 XML 分析程序认为这些字段是实际的 XML 标记。在这种情况下,将不会正确表示 XML 数据,可能形成畸形的 XML 数据,并很可能导致分析程序失败。
此外,在一个记录中返回 HTML 数据时一定要小心。这种数据也可能危及返回的 XML 数据的完整性。发生这种情况的主要原因是,HTML 通常是畸形的。用这种方式插入 HTML 将导致返回畸形的 XML。如果发生这种情况,ROPE 将报告一个错误,指出产生了一个错误的 SOAP 返回值。要解决这个问题,应该将 HTML 数据封装在一个 CDATA 区域中。
二进制数据
如果二进制数据是以 Base64 编码的,则可以通过 SOAP 传送它。因为 SOAP 是基于文本的,所以将使用 SOAP 把二进制数据作为其基于文本的表示加以传送。该数据在到达客户机以后,仍可将它解码为二进制数据。在本实现中不讨论 Base64 编码算法。
大型记录集
如果使用解决方案 1,则使用这种消息收发类型的大型记录集返回将证明是耗时的,因为该函数必须分析全部字段值来确保生成形式良好的 XML。因此,在使用解决方案 1 时,一定要测试该解决方案同时满足服务器和客户机实现的性能要求。
作为数据类型的记录集
记录集并不是“服务描述符语言”(SDL) / SOAP 中的合法数据类型。SOAP Toolkit Wizard 最初在 SDL 中将记录集表示为一个 "string"。开发人员必须修改 SDL 来表示记录集的结构。这样,他就将此数据结构表示为返回数据的真正 XML 表示。
分层记录集
当以 XML 格式保存分层记录集(数据的形状)时,存在两个局限性。一个局限性是,如果分层记录集包含了未决更新,就不能保存为 XML;另一个局限性是不能保存参数化的分层记录集。有关详细信息,请参阅 http://msdn.microsoft.com/library/psdk/dasdk/mdam5zz9.htm(英文)。
ADO 版本
这些示例都是使用 ADO 版本 2.5 构建的。在客户机和服务器上使用有冲突的版本可能会产生不可预料的结果。
摘要:本文提供了用 SOAP 引出 Web Service 中现有的 COM 对象的准则。本文通过研究返回 ADO 记录集的两种不同方案说明了这些准则,最后讨论了每种解决方案的优缺点,以及在对数据进行打包和检索时可能出现的其它问题。
单击此处可找到相关文档,并可下载 SOAP Toolkit for Visual Studio 6.0 。(英文)
目录
Web Service 概念的引入带来了一种全新的、强有力的方法,用于在 Internet 上进行方法调用。开发人员现在可以利用“简单对象访问协议”(SOAP) 技术,使用户远程调用 Internet Information Services (IIS) 服务器上现有 COM 对象的方法。
因为这种新的调用进程是基于消息的,所以它在语言和平台方面是未知的。意思是说开发人员可用他们最钟爱的语言来开发客户机代码和服务器对象。Microsoft® SOAP Toolkit 使开发人员能够在 Microsoft Windows® 2000 服务器上使用这项技术快速而容易地在 Web 上引出方法。
在将 Microsoft SOAP Toolkit 添加到新的开发工具库以后,我想测试的第一项内容就是,SOAP 与我现有的 Web 应用程序结合得怎么样,以及有多么容易。许多现有的 COM 对象和 Web Service 都通过 Microsoft ActiveX® Data Objects (ADO) 记录集将信息返回到客户机。我开发的用来跟踪我的音乐库的 Web Service 就属于这种情况。
一个 SQL Server™ 数据库包含了我关于艺术家和歌曲的记录。因为 SOAP Toolkit 能够帮助在 Internet 上引出我的服务(并且几年前就出现了在 ASP 页面或简单客户机应用程序中访问此功能的技术),所以我认为,通过 SOAP 将我的音乐库服务引出是一件相当容易完成的事情。
在安装了 SOAP Toolkit 并将它的组件注册以后(有关正确安装必需的 SOAP 文件的信息,请参阅 SOAP Toolkit 下载包中的 SOAP Toolkit 文档),我就准备通过 SOAP 引出我的第一个 COM 对象。首先运行 SOAP Toolkit Wizard,并使它指向我的列表中的第一个 COM 对象 — 该对象返回我的音乐库中音乐家的一个列表(以及其它一些有帮助的方法)。该向导返回下面的屏幕(图 1)。
图 1. SOAP Toolkit Wizard
我最感兴趣的方法用红色加以高亮显示!发生了什么事?
此处显而易见的问题实际上根本就不成为问题。它只要求对 SOAP 到底是什么,以及它如何调用方法并返回数据有一定的了解。有了这种了解,您就可以完成某些相当强大的编程目标。
请注意位于方法右侧的“注”区域中明确显示:
“用红色高亮显示的方法要么返回未知的数据类型,要么包含未知的参数变量类型。如果您一定要选择它,则会在 SDL 文件中将有问题的类型定为数据类型 'string'。”
回想一下,我记得这些高亮显示的方法返回的是 ADO 记录集。因此,我要么必须为该对象编写一段包装代码,要么必须重写我的 COM 对象。
深入研究 SOAP 的工作方式证实,SOAP 的意图就是在对分布式对象技术没有任何共享理解的情况下传递消息。ADO 记录集对象是特定于 Microsoft COM 技术的。因为 SOAP 的一个主要目标就是平台和技术之间的互操作性,所以我们的返回 SOAP 程序包不可能返回 ADO 记录集这样的对象。
为了说明这种基于消息的技术,让我们进一步考察实际的 SOAP 程序包到底返回什么。下面的 SOAP 程序包返回值取自 SOAP Toolkit 的 Microsoft Visual Basic® 样例,该样例调用方法 GetServerTime:
正如我们在上面的样例代码节选中看到的那样,SOAP 的返回值是基于 XML 文本的消息。不可能通过这种范例返回像 ADO 记录集这样的对象。另一方面,过去我已将 ADO 记录集转换为 XML;那么采用什么办法来改变这种情况呢?实际上,我可以将 ADO 记录集转换为 XML,并通过 SOAP 将它传递给我的客户机!简单的返回值可能类似以下代码:
<artists> <artist>ABBA</artist> <artist>Aeromsmith</artist> <artist>Alice Cooper</artist> <artist>Bad Company</artist> <artist>Barry White</artist> <artist>Brash</artist> ...end so on </artists>
这样我们就发现了一种方法,使用 SOAP 和一点小创新就可以返回某个对象的数据,如 ADO 记录集。在下一节,我们将探索不同的方法来完成这一任务。
通过选择方法 GetAllArtists 并选择使用 ASP 解决方案,我将生成我的服务所必需的源文件。在下一节中讨论各种可能的解决方案时,将会用到这些文件。
注: SOAP Toolkit 文档更详细地说明了使用 ASP 解决方案与使用 ISAPI 解决方案之间的差异。
将记录集打包
虽然下面的每个解决方案都为我的问题提供了解决方案,但每个方案都各有其优缺点,我将在下面对此进行详细说明。
我们已经用 SOAP Toolkit Wizard 创建了源文件。现在我们将看一看它所生成的 ASP 接口文件(因为我们选择了使用 ASP 监听程序解决方案)。之所以选择使用 ASP 解决方案,是因为它具有这样的灵活性,即它允许在 COM 对象完成的处理之前和之后添加其它代码。
注: 相比之下,ISAPI 监听程序解决方案最适合调用不需要其它代码处理的 COM 对象的情况,而我们现在需要进行这种处理。虽然 ISAPI 监听程序不像 ASP 监听程序那样灵活,但它能够提供比 ASP 监听程序更好的性能。另外,如果我们能够修改现有的 COM 对象来返回 XML,或者编写另一个 COM 对象来包装现有对象的功能,并使该包装对象返回 XML,则 ISAPI 解决方案可能会是更好的选择。
向导生成的函数如下所示:
Public Function GetAllArtists () Dim objGetAllArtists Set objGetAllArtists = Server.CreateObject("Radioweb.clsSongs") GetAllArtists = objGetAllArtists.GetAllArtists() '在此处插入其它代码 Set objGetAllArtists = NOTHING End Function
请记住,虽然 GetAllArtists 将返回一个 ADO 记录集,但是因为这并不是一种符合 SOAP 的数据类型,所以 SOAP Toolkit Wizard 会指定缺省的返回数据类型 "string"。因此,为了返回我们的数据,该字符串返回值将必须是记录集的一种 XML 表示。
现在我将概要介绍两种不同的解决方案。解决方案 1 实现了一个单独的函数,将记录集转换为一种 XML 表示。解决方案 2 则使用 ADO 记录集的 Save 方法,通过 ADO Stream 对象将数据保持为 XML 格式。
本文的后半部分会说明每种解决方案的优缺点。
对于第一种解决方案,我们将实现“粘合代码”,以方便从记录集到 XML 字符串的转换,如下所示:
Public Function GetAllArtists () Dim objGetAllArtists Set objGetAllArtists = Server.CreateObject("Radioweb.clsSongs") GetAllArtists = GetXMLFromADORS(objGetAllArtists.GetAllArtists()) '在此处插入其它代码 Set objGetAllArtists = NOTHING End Function
其中 GetXMLFromADORS 如下所示:
Public Function GetXMLFromADORS(ByVal objADORS) Dim objField Dim strXMLString Dim strFieldName Dim strFieldValue strXMLString = "<RECORDSET>" & vbCrLf Do while not objADORS.EOF strXMLString = strXMLString & vbTab & "<RECORD>" & vbCrLf For each objField in objADORS.Fields strFieldName = objField.name strFieldValue = objADORS(strFieldName) If instr(1, strFieldValue, "/") Or _ instr(1, strFieldValue, ">") Or _ instr(1, strFieldValue, "<") Then strFieldValue = CDATAit(strFieldValue) End If strXMLString = strXMLString & vbTab & vbTab & _ "<" & strFieldName & ">" & strFieldValue & "</" & _ strFieldName & ">" & vbCrLf Next objADORS.MoveNext strXMLString = strXMLString & vbTab & "</RECORD>" & vbCrLf Loop strXMLString = strXMLString & "</RECORDSET>" & vbCrLf GetXMLFromADORS = strXMLString End Function
GetXMLFromADORS 函数将不会像 ADO Save 方法(将在解决方案 2 打包中讨论)那样强健,但它不要求在客户机上安装 Microsoft Data Access Components (MDAC)。其原因是,客户机只须分析简单的 XML 结构,而不必将 XML 转换为 ADO 记录集。
我还实现了 CDATAit 函数,它将防止插入可能形成畸形 XML 的字符串。在调用过程内部可根据需要使用此逻辑,GetXMLFromADORS 函数的情况也是这样。在上面的示例中,我只对 ">"、"<" 和 "/" 进行了检查。
Private Function CDATAit(ByVal strData) CDATAit = "<![CDATA[" & strData & "]]>" End Function
GetAllArtists 函数现在将返回类似下面的结果:
GetAllArtists 函数现在返回了形式良好的 XML 记录集。请注意,我们的 XML 中不存在任何 CDATA 区域。如果任何艺术家的名字包含了我们所查找的字符("/"、"<" 或 ">"),CDATAit 函数就会将它们包装在 CDATA 区域,以确保形成形式良好的 XML。
我们必须进行的唯一额外更改就在向导所生成的“服务描述语言”(SDL) 文件中。该文件是用来描述我们正在引出的 Web Service 的 XML 文件。它将位于您指示向导将您的文件保存在其下的同一文件夹中。必须修改向导为我们的返回数据生成的数据类型 — 以便“远程对象代理引擎”(ROPE)能够按 XML 正确分析数据,需要指明数据类型是一种 XML 方案。请注意,客户机代码仍将把返回数据视为由某种 XML 结构表示的字符串变量。
在这种情况下,将生成如下所示的 SDL 文件:
将需要修改描述数据返回值的部分。您将注意到,这在我们的 SDL 文件中是这样描述的:
<element name='GetAllArtistsResponse'> <type> <element name='return' type='dt:string'/> </type> </element>
我们的返回值不再是由 SDL 所描述的字符串,而是表示记录集的一个结构。因此,对此文件的修改需要将此返回值表示为一个结构,而不是一个字符串。可以通过将该文件更改为以下形式来完成这一工作:
现在请注意,我们是将 'return' 元素作为一种 'MyReturnStruct' 类型返回,而不是作为一个字符串返回。
'MyReturnStruct' 是我们将要定义的一种结构元素。它将是我们返回的结构的父结构。请记住,该结构必须具有如下的形式:
<RECORDSET> <RECORD> <artist>Some artist</artist> </RECORD> .. .. .. </RECORDSET>
因此,'MyReturnStruct' 是一种定义如下的元素:
<element name='MyReturnStruct'> <type> <element name='RECORD' type='ArtistStruct'/> </type> </element>
请注意,'MyReturnStruct' 具有一个名为 'RECORD' 的元素,其类型进一步定义为 'ArtistStruct'。这是必需的,因为子元素 <artist> 实际上是 <RECORD> 的一个子代,而后者又是 <RECORDSET> 的一个子代。
然后将 'ArtistStruct' 定义为如下形式:
<element name='ArtistStruct'> <type> <element name='artist' type='dt:string'/> </type> </element>
其中,数据类型为 'string' 的元素 'artist' 代表结构中的单个艺术家。
在研究用来分析这些数据的方法之前,让我们考察一下能够获得相同结果的另一种方法。
将记录集转换为形式良好的 XML 字符串的另一种方法是,使用 ADO 将记录集保存到作为目的地的一个 ADO Stream 对象中,并保持为 XML。
注: MSDN Magazine 就这一主题有一篇更为深入的文章,"使用 ADO 来创建基于 XML 的记录集",作者为 Don Box (http://msdn.microsoft.com/library/periodic/period00/com0700.htm)(英文)。Hanson Brothers 还实现了一个深入的示例,请参阅 http://msdn.microsoft.com/xml/articles/hbinterop_core.asp(英文)。
按这种方式返回记录集的粘合代码稍微容易一点。我已在 ASP 接口文件中实现了以下函数:
Public Function GetXMLStreamFromADORS(ByVal objADORS) Dim oStream 'As ADODB.Stream Set oStream = Server.CreateObject("ADODB.Stream") objADORS.Save oStream, adPersistXML GetXMLStreamFromADORS = CDATAit(oStream.ReadText) Set oStream = Nothing End Function
我已在此 ASP 文件的开头定义了以下常数:
CONST adPersistADTG = 0 CONST adPersistXML = 1
只包括了这两个常数,因为它们是仅会用到的两个常数。如果您选择了 Microsoft Visual Studio® 6.0,则您也可以使用它附带的 adovbs.inc 文件。
现在,应该通过修改函数 GetAllArtists() 的代码,使其实现新的帮助器函数 GetXMLStreamFromADORS,如下所示:
Public Function GetAllArtists() Dim objGetAllArtists Set objGetAllArtists = Server.CreateObject("Radioweb.clsSongs") GetAllArtists = GetXMLStreamFromADORS(objGetAllArtists.GetAllArtists()) 'Insert additional code here Set objGetAllArtists = NOTHING End Function
之所以必须添加 CDATAit 函数(在解决方案 1 打包中说明),是因为必须将此 XML 字符串表示为一个字符串,以便通过 ROPE 正确地将其返回。我们也可以像在解决方案 1 中那样修改 SDL,但因为这种结构更难于解释和预测,所以它可能非常令人厌烦并容易出错。通过使用 CDATAit 函数,我们可以用数据类型 'string' 在 CDATA 区域返回该结构。此外,我们还可以在将来的记录集实现中使用这些函数,而且不必修改 SDL 文件。
该函数实际上通过 ADO Stream 对象为我们返回记录集的 XML 表示,如下所示:
我们随后将按字符串检索数据,就像在我们的 SDL 中定义的那样。请记住,我们的 SDL 是按如下方式来定义返回值的:
<element name='GetAllArtistsResponse'> <type> <element name='return' type='dt:string'/> </type> </element>
因为我们的数据实际上包装在 CDATA 区域中,所以可以保证,当从 Stream 对象返回时,我们的数据是合法的 XML。
注: 这些示例中所使用的函数,如 GetXMLStreamFromADORS 和 GetXMLFromADORS,可以很容易地放入一个 include 文件中,实现类似 COM 对象(返回 ADO 记录集)的 ASP 接口文件都可包括该文件。在您的 Web Service 的整个生存期中,这种可重用性都将有助于今后的开发。
检索记录集
既然已成功地将返回数据打包,那么必须在客户机应用程序中正确分析这些数据。根据您决定使用哪一种上述解决方案,可以有几种不同的方法来实现这一点。我们将单独考察每个解决方案。
解决方案 1 检索
解决方案 1 中返回的数据是记录集的一种简单 XML 数据表示。现在,我们将使用 Microsoft XML Document Object Model (DOM) 来检索此数据,以便在内部循环处理每一条记录,XML 对这一过程的表示如下:
Private Sub Command1_Click() Dim aryArtists() As String Dim oRope as New Rope.Proxy oRope.LoadServicesDescription icURI, "http://MyServer/GetArtists.xml" GetArtistsFromXML oRope.GetAllArtists(), aryArtists() '现在 aryArtists() 是包含我们的艺术家的一个数组 Set oRope = Nothing End Sub Private Sub GetArtistsFromXML(ByVal sXML As String, ByRef aryArtists() As String) Dim oXML As New MSXML.DOMDocument Dim oXMLDocument As MSXML.IXMLDOMElement Dim iCount As Integer Dim x As Integer oXML.loadXML sXML Set oXMLDocument = oXML.documentElement iCount = oXMLDocument.childNodes.length ReDim aryArtists(iCount - 1) For x = 0 To iCount - 1 aryArtists(x) = oXMLDocument.childNodes.Item(x).Text Next Set oXML = Nothing Set oXMLDocument = Nothing End Sub
Command1_Click() 方法使用 ROPE 调用服务。因为该服务按解决方案 1 中指定的方式返回数据,所以现在就可以传递包含在我们的 XML 字符串中的返回数据。我们按值传递该字符串,并按引用传递一个将被置入 Public Sub GetArtistsFromXML 中的空数组。此过程置入数组并将它返回给调用过程。数组变量 aryArtists() 现在包含我们的艺术家的一个数组。
解决方案 2 检索
为了将此 XML 数据返回给一个 ADO 记录集,我们需要将数据加载到 XML DOM 中,然后使用 ADO 记录集的 OPEN 方法创建艺术家的记录集:
Private Sub Command1_Click() Dim oRS As ADODB.Recordset Dim oRope as New Rope.Proxy oRope.LoadServicesDescription icURI, "http://MyServer/GetArtists.xml" Set oRS = CreateADORSFromXML (oRope.GetAllArtists()) '现在 oRS 是包含我们的艺术家数据的一个分离的 ADO 记录集 Set oRope = Nothing Set oRS = Nothing End Sub Private Function CreateADORSFromXML(ByVal sXML As String) As ADODB.Recordset Dim oXML As New MSXML.DOMDocument Dim oRS As New ADODB.Recordset oXML.loadXML sXML oRS.Open oXML Set CreateADORSFromXML = oRS Set oRS = Nothing Set oXML = Nothing End Function
在 Command1_Click() 事件中,ROPE 对象调用我们的方法,该方法会就像在解决方案 2 中那样返回我们的 XML 字符串。通过调用 CreateADORSFromXML 函数,就可以设置一个对象引用从 SOAP 返回数据创建一个分离的记录集。
虽然每个解决方案都为我们的问题提供了答案,但有时您可能想使用的是某一种方案,而不是另一种。下面的两个表提到了几个示例。
表 1. 解决方案 1 的优缺点
优点 | 缺点 |
因为不使用 ADO,所以不要求在客户机上安装 MDAC。因此,开发人员不必将 MDAC 打包到应用程序的发行包中。 | GetXMLFromADORS 函数是用来处理简单记录集的。更复杂的记录集结构可能要求使用 ADO 为您的解决方案重新创建 XML。用您的数据结构测试此函数,以确保能够正确表示您的记录集。 |
GetXMLFromADORS 函数必须分析每个字段中的文本来构建 XML 表示。对于大型记录集,这可能是一个时间较长的过程,因为该函数要查找可能导致畸形的 XML 的字符,以便“净化”文本。 |
表 2. 解决方案 2 的优缺点
优点 | 缺点 |
将 COM 对象返回的原始记录集从 XML 重新构建成一个 ADO 记录集对象。这样,开发人员就可以针对 ADO 记录集编写代码,就好象它是原始对象一样。 | 必须在客户机上安装 MDAC。这将增加您的安装程序和分发文件的大小。 |
与 GetXMLFromADORS 函数相比,能够更准确地表示 XML 记录集的 ADO 持久性。 | |
数据的真正面对对象的表示。 |
一种解决方案可能比另一种工作得更好,这取决于您的需要。如果您无法控制您的客户机是否安装 MDAC,则您可能要选择解决方案 1 的方法。如果数据的真正的对象表示是必需的,而且已经知道客户机上安装了 MDAC,则应该选择解决方案 2。
当实现这些类型的转换时,这两种解决方案都可能会产生一些问题。这些问题既适用于数据打包,也适用于数据检索。还可能出现其它问题,这取决于数据的完整性、结构和大小等等。
数据字段
当使用解决方案 1 时,数据字段不能包含可能导致畸形 XML 的字符(<、>、/,等等)。函数 GetXMLFromADORS 使用逻辑
"<" & strFieldName & ">"
这可能导致畸形的 XML 表示。像 <、> 和 / 这样的字符,可能导致 XML 分析程序认为这些字段是实际的 XML 标记。在这种情况下,将不会正确表示 XML 数据,可能形成畸形的 XML 数据,并很可能导致分析程序失败。
此外,在一个记录中返回 HTML 数据时一定要小心。这种数据也可能危及返回的 XML 数据的完整性。发生这种情况的主要原因是,HTML 通常是畸形的。用这种方式插入 HTML 将导致返回畸形的 XML。如果发生这种情况,ROPE 将报告一个错误,指出产生了一个错误的 SOAP 返回值。要解决这个问题,应该将 HTML 数据封装在一个 CDATA 区域中。
二进制数据
如果二进制数据是以 Base64 编码的,则可以通过 SOAP 传送它。因为 SOAP 是基于文本的,所以将使用 SOAP 把二进制数据作为其基于文本的表示加以传送。该数据在到达客户机以后,仍可将它解码为二进制数据。在本实现中不讨论 Base64 编码算法。
大型记录集
如果使用解决方案 1,则使用这种消息收发类型的大型记录集返回将证明是耗时的,因为该函数必须分析全部字段值来确保生成形式良好的 XML。因此,在使用解决方案 1 时,一定要测试该解决方案同时满足服务器和客户机实现的性能要求。
作为数据类型的记录集
记录集并不是“服务描述符语言”(SDL) / SOAP 中的合法数据类型。SOAP Toolkit Wizard 最初在 SDL 中将记录集表示为一个 "string"。开发人员必须修改 SDL 来表示记录集的结构。这样,他就将此数据结构表示为返回数据的真正 XML 表示。
分层记录集
当以 XML 格式保存分层记录集(数据的形状)时,存在两个局限性。一个局限性是,如果分层记录集包含了未决更新,就不能保存为 XML;另一个局限性是不能保存参数化的分层记录集。有关详细信息,请参阅 http://msdn.microsoft.com/library/psdk/dasdk/mdam5zz9.htm(英文)。
ADO 版本
这些示例都是使用 ADO 版本 2.5 构建的。在客户机和服务器上使用有冲突的版本可能会产生不可预料的结果。