已启用 XML Web 服务的 Office 文档
Microsoft Office XP 和 .NET Web 服务已经联姻,您准备好了吗?在 B2B 电子商务网络世界中,为什么不将业务处理流程同人们在计算上所做的一切结合起来,从而将 Web 服务的强大动力传递到最终用户?我在谈论什么?哦,我在谈论一个看起来有些象图 1 所示的 Excel 电子表格。
图 1:已启用 Web 服务的 Excel 电子表格
这可不是一份普通的电子表格。它使用 UDDI 来查找公司的地址,并使用目录 Web 服务来查找产品信息。当您单击“发送”按钮后,它还会对 XML 电子表格格式进行 XML 转换,生成一个 RosettaNet(英文)PIP 3 A4 订单申请格式。
键入要购买其商品的公司名称后,单击“查找”按钮,电子表格下的某个 VBA 代码将调用 UDDI 并完成地址部分其余的内容。例如,键入 Microsoft,单击“查找”,您将在“卖方”域中看到下面的内容:
图 2:“卖方”域
键入数量,例如 23,并在说明域中键入 Pear,然后按 Tab,某个 VBA 代码将查询 SOAP 目录 Web 服务,以查看是否有匹配的产品,并列出详细信息。在本例中,我已将目录 Web 服务连接到 Northwind 数据库,因此它将返回以下信息:
图 3:电子表格订购部分的详细情况
在本例中,还列出了说明并将其转换为一个连接到详细介绍此产品的 HTML 页面的链接。
如果找到多个产品但没有一项完全满足您键入的条件,则将提供一个选项下拉列表。例如,如果键入豆腐,您将看到下列选项:
图 4:未找到完全匹配项时所提供的多个选项的示例
如果您选择其中一项,则将提供此项的详细信息。
完成后,单击“发送”按钮,即生成 RosettaNet PIP 3 A4 XML 订单格式,订单也同时发出。
如何实现?
通过访问“工具”菜单,选择“宏”,然后选择“Visual Basic 编辑器”,您可以浏览 VBA 代码。ThisWorkbook 下的某些代码会对电子表格中的变更做出响应,特别是当您删除说明时,Workbook_SheetChange 事件将清除一个项目行;当您将“说明”域移入“SKU”域时,Workbook_SheetSelectionChange 事件将调用 FindProduct()。如果 FindProduct 返回 XMLNode,将从该节点拉出相关的域,以填充项目行的其他详细信息。
有关 UDDI find_business 调用的工作方式,您可以查阅我的另一篇文章 UDDI:一种 XML Web 服务。如果找到一项业务,在 UDDI 响应的 /businessInfo/contacts/contact/address/ 部分找到的 addressLines 将被用来填充“卖方”地址块。
目录 Web 服务
Catalogs 模块中的 FindProduct 函数将使用包含搜索项的 URL 参数来调用目录服务 URL。它会得到一个 SOAP 响应并首先检查是否与 /Envelope/Body/Fault 匹配,如果返回的不是 Fault,它将继续打开 <CatalogQueryResult>,以检查返回项目中的 ProductName 属性是否与给定的范围相匹配。它还会在可视区域以外的页面创建下拉选项列表。您可以在“数据”菜单中选择“验证”来查看下拉列表的工作方式。
目录 Web 服务十分简单。.aspx 入口点将创建一个 CatalogSearch 对象,该对象在 search.cs 中定义,并调用 Execute 按下列方式传递 HttpResponse 输出流:
<%@Language="C#" src="search.cs" Debug="true" %>
<%
Response.ContentType = "text/xml";
string term = Request.QueryString["term"];
if (term != null) {
CatalogSearch s = new CatalogSearch(term);
s.Execute(output);
} else {
Response.Write("<Empty/>");
}
%>
真正的乐趣将从 Execute 方法开始。它是一个捆绑在 XmlTextWriter 中的、十分简单的 SQL 托管提供程序代码,能够从 SQL SELECT 语句返回特定的域。因此,它基本上是一个通过 DataReader 向 XmlTextWriter 写入以下内容的 while 循环:
public void Execute(TextWriter stm)
{
XmlTextWriter xw = new XmlTextWriter(stm);
xw.WriteStartElement("Envelope", "http://schemas..../envelope/");
xw.WriteStartElement("Body", "http://schemas..../envelope/");
try {
String const = "server=localhost;uid=sa;pwd=;database=northwind";
SQLConnection con = new SQLConnection(constr);
con.Open();
IDataReader reader;
String query = "SELECT ProductName,UnitPrice,QuantityPerUnit," +
"SupplierID,ProductID FROM Products WHERE " +
"ProductName LIKE '%" + term + "%'";
SQLCommand cmd = new SQLCommand(query, con);
cmd.Execute(out reader);
string funNamespace = "urn:schemas-b2b-fun:catalogs";
xw.WriteStartElement("CatalogQueryResult", funNamespace);
while (reader.Read())
{
xw.WriteStartElement("item");
xw.WriteAttribute("ProductName", reader.GetString(0));
xw.WriteAttrDecimal("UnitPrice", reader.GetDecimal(1));
xw.WriteAttribute("UnitOfMeasure", reader.GetString(2));
xw.WriteAttribute("SKU", "S"+reader.GetInt32(3)+
"-P"+reader.GetInt32(4));
xw.WriteEndElement();
}
xw.WriteEndElement();
con.Close();
} catch (Exception e) {
xw.WriteStartElement("Fault");
xw.WriteElementString("faultcode","500");
xw.WriteElementString("faultstring",e.ToString());
xw.WriteEndElement();
}
xw.WriteEndElement();
xw.WriteEndElement();
xw.Close();
}
URL http://localhost/catalog/search.aspx?term=豆腐将返回以下结果:
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<CatalogQueryResult xmlns="urn:schemas-b2b-fun:catalogs">
<item ProductName="豆腐" UnitPrice="23.25"
UnitOfMeasure="每包装 40 - 100 克" SKU="S6-P14"/>
<item ProductName="长保鲜期豆腐" UnitPrice="10"
UnitOfMeasure="每包装 5 千克" SKU="S4-P74"/>
</CatalogQueryResult>
</Body>
</Envelope>
这可能是使用 .NET 框架从 SQL Server 获得 XML 的最有效的方式。粗略计算,在我的 Dell PowerEdge 2400 上每秒可获得其中的 80 到 90 个这样的 XML。
“发送”按钮
SendOrder() 函数可按电子表格中选定范围的单元格的 XML 表示形式加载 XML 文档。通过下列神奇的 VBA 代码行便可以实现这一切:
With ActiveSheet
Set sourcexml = New MSXML2.DOMDocument
sourcexml.loadXML .Range("B1:N34").value(xlRangeValueXMLSpreadsheet)
End With
这将返回一个巨大的 XML 程序块,完整说明有关电子表格中选定范围的单元格的全部信息。以下是 XML 程序块的代码片断:
<Workbook>
<Worksheet>
<Table>
<Row>
<Cell ss:StyleID="s23"><Data ss:Type="Number">23</Data>
<NamedCell ss:Name="Item"/></Cell>
<Cell ss:MergeAcross="4" ss:StyleID="m31209522"
ss:HRef="http://eshop.msn.com/category.asp?catId=170">
<Data ss:Type="String">鲍勃叔叔的有机干梨
</Data></Cell>
<Cell ss:StyleID="s52">
<Data ss:Type="String">S3-P7</Data></Cell>
<Cell ss:StyleID="s26">
<Data ss:Type="Number">30</Data>
<NamedCell ss:Name="UnitPrice"/></Cell>
<Cell ss:StyleID="s27">
<Data ss:Type="String">每包装 1 磅(12 个)</Data></Cell>
<Cell ss:StyleID="s37">
<Data ss:Type="Number">690</Data></Cell>
<Cell ss:StyleID="s49"/>
</Row>
</Table>
</Worksheet>
</Workbook>
我们可以使用 XSL 将它转换为以下格式:
<PurchaseOrder xmlns="http://www.rosettanet.org">
<deliverTo>
<PhysicalAddress>
<cityName>Seattle, WA, USA 98111</cityName>
<addressLine1>Airport Chocolates</addressLine1>
<addressLine2>2711 Alaskan Way</addressLine2>
<regionName>USA</regionName>
</PhysicalAddress>
</deliverTo>
<ProductLineItem>
<ProductQuantity>23</ProductQuantity>
<productUnit>
<ProductPackageDescription>
<ProductIdentification>
<GlobalProductIdentifier>S3-P7</GlobalProductIdentifier>
</ProductIdentification>
</ProductPackageDescription>
</productUnit>
<Description>鲍勃叔叔的有机干梨</Description>
<requestedPrice>
<FinancialAmount>
<GlobalCurrencyCode>USD</GlobalCurrencyCode>
<MonetaryAmount>30</MonetaryAmount>
</FinancialAmount>
</requestedPrice>
</ProductLineItem>
<thisDocumentGenerationDateTime>
<DateTimeStamp>2001-03-15T00:00:00.000</DateTimeStamp>
</thisDocumentGenerationDateTime>
</PurchaseOrder>
注意:按照 RosettaNet PIP 3 A4 订单申请规范,这可能不是一份技术上完整的申请,但您可以由此得到启发。
提高此转换的强壮性的技巧在于命名要从中导出数据的重要单元格。这可以通过 XSLT 转换中下列样式的 XPath 表达式来实现:
select="/Workbook/Worksheet/Table/Row/Cell[NamedCell[@ss:Name='City']]
此特定表达式将查找名称为 City 的单元格。而样式表的其余部分将被忽略。有关详细信息,请参阅 XLToPO.xsl。
尝试使用
要运行此函数,您只需安装 MSXML 3.0 并获取一份 Northwind 数据库。演示代码将按以下方式连接至 SQL Server:
SQLConnection("server=localhost;uid=sa;pwd=;database=northwind");
如果 Northwind 数据库位于其他位置,您需要更改此代码位。
PO.xsl 电子表格假定目录服务位于:
http://localhost/catalog/search.aspx
您需要在本地计算机的名为 catalog 的虚拟目录中安装 Web 服务 search.aspx、search.cs 和 XLToPO.xsl,或者将电子表格指向别处。
要编辑电子表格,您必须关闭保护(可以使用“工具/保护”子菜单关闭)。
下一步
您可能希望将供应商的目录服务绑定保存到 UDDI 中。有一些 VBA 代码能帮您做到这一点。该代码将查找可识别的目录服务 serviceInfo(通过 serviceKey),如果找到,代码将使用包含在 serviceDetails 中的 accessPoint。我在本演示中所使用的伪目录 API 并没有作为 UDDI 中的已知服务类型进行注册。
使用 Office Smart Tag 来做类似的工作可能会是一种比较有趣的体验。有关详细信息,请参阅 http://msdn.microsoft.com/office/(英文)上的 Smart Tag SDK。
Chris Lovett 是 Microsoft XML 小组的编程经理。