在运行时使用 uddi
karsten januszewski
microsoft corporation
2001 年 12 月
摘要:本文概述了在运行时使用 uddi,讨论了 uddi(公共注册表和 uddi 服务在 microsoft windows .net server 中提供)如何用作 web 服务的基础结构以支持客户端应用程序。 目录
- 简介
- uddi 运行时基础结构
- 示例方案
- 创建 web 服务:c# .net .asmx
- 使用 web 服务:c# windows 窗体 .net 客户端
- 其他方案
- 总结
简介
uddi(通用说明、发现和集成)通常称为 web 服务的“黄页”。虽然黄页这一类比对于帮助理解很有用,但它不能完整体现 uddi 是如何并入基于 web 服务的软件体系结构中的。黄页类比只涉及 uddi 的设计时用法,即通过基于关键字、类别或接口的搜索来查找和使用 web 服务的功能。从设计时角度来看,黄页类比十分准确:就象黄页将企业及其电话号码分类并编成目录一样,uddi 将提供者及其 web 服务分类并编成目录。开发人员可以在 uddi 中查找 wsdl 文件和入口点,然后将这些 web 服务并入客户端应用程序。
不过,uddi 不仅仅提供设计时支持。黄页类比并没有提及 uddi 如何支持运行时。发现过程结束后,uddi 将扮演一个很重要的角色。在运行时通过编程方式查询 uddi 这一功能使 uddi 可以用作构建可靠而稳固的 web 服务应用程序的基础结构。uddi 运行时基础结构
将 web 服务集成到客户端应用程序中后,需要考虑可能会遇到的问题,其中一个关键问题是无法预测或检测提供 web 服务的提供者的故障,或者从故障中恢复。如果 web 服务出现故障,客户端应用程序可以采取什么措施?应用程序如何适当且动态地从失败的 web 服务调用中恢复?
同样,从 web 服务提供者的角度看,web 服务的所有者如何动态更新更改内容?我们需要考虑将 web 服务移到新服务器的情况。如何将此更改通过有效方式通知到 web 服务的客户端?所有者如何在运行时分发此信息,而不会导致 web 服务的所有客户端中断?
在上述情况下,uddi 在提供基础结构以支持运行时 web 服务方面能够扮演十分重要的角色。uddi 通过定义调用规则来解决这些“服务质量”问题,该调用规则包括缓存绑定信息(如 web 服务的入口点)以及针对此特定实现方式的其他参数。发生故障时,客户端可以发出运行时 uddi 查询,用最新信息刷新缓存的信息。
上述规定的模式如下: - 在 uddi 中查找 web 服务。使用此 web 服务(以 uddi tmodel 表示)的 wsdl 文件以及入口点和其他配置信息的实现细节,所有这些信息都包含在 uddi bindingtemplate 中。
- 为特定的 web 服务准备一个客户端应用程序。在客户端应用程序中,缓存 web 服务的唯一 bindingkey,以便在首次使用该应用程序时,从 uddi 检索所需的所有信息。
- 当应用程序调用远程 web 服务时,使用从 uddi web 注册表获得的缓存数据。
- 如果调用失败,则使用 bindingkey 值和对 uddi 注册表的 get_bindingtemplate api 调用以获取新的绑定信息。
- 对新旧信息进行比较:如果不同,则重试失败的调用。如果重试成功,则用新数据替换缓存的数据并存储新数据以便以后调用。不过,如果返回的绑定信息相同,则提供者不进行任何更新,且应用程序将发生错误。同样,如果有新的绑定信息,但调用仍然失败,应用程序也会发生错误。
从 web 服务提供者的角度来说,提供者应当知道何时可以更新该 web 服务的 uddi 项。当 web 服务的提供者需要将流量重定向到新位置或者需要备份系统时,提供者只需激活备份系统,然后在 uddi 注册表中更改入口点。此方法称为失败时重试,为客户端提供了一种在运行时从故障中恢复的机制。示例方案
我们可以看一个示例,以了解此模式的工作原理。此示例方案涉及的是一家虚构公司的方案,该公司需要向其内部部门提供实时销售数据。因此,此 web 服务不能公开,而只在防火墙内部使用。
首先,我们需要一个 web 服务。在这种情况下,我们将提供一个非常简单的 web 服务,它只支持 getsalestotalbyrange 一种方法,这种方法使客户端能够获取一定日期范围内实时销售数据的瞬态图。
接下来将创建使用此 web 服务的客户端。我们将客户端配置为可以缓存入口点和 bindingkey 信息,并为客户端设置一种机制,以便在发生故障时从 uddi 注册表刷新客户端缓存。创建 web 服务:c# .net .asmx
microsoft .net 框架大大简化了 web 服务的编写工作。在本例中,我们将创建一个简单的 web 服务,它只包含 getsalestotalbyrange 一种方法,该方法使用两个日期作为输入参数,并返回两个参数。下面是一个实现了此目的的 .asmx 页,salesreport.asmx:<%@ webservice language="c#" class="salesreportusa.salesreport" %>using system;using system.web.services;namespace salesreportusa{ [webservice(namespace="urn:mycompany-com:salesreport-interface")] public class salesreport : system.web.services.webservice { [webmethod] public double getsalestotalbyrange ( system.datetime startdate, system.datetime enddate ) { return 5000.00; } }}
此页应添加到虚拟目录中。要使客户端示例工作,请创建一个名为 salesreportusa 的虚拟目录 (http://localhost/salesreportusa/salesreport.asmx)。请注意,此 web 服务始终返回 5000.00 作为返回值。(要是销售报表能够有这样的可预见性就好了!)真实的应用程序应当使用数据库调用来检索此信息。对于本示例,只需要一个硬编码的值。
部署此 web 服务的下一步是在 uddi 注册表中注册该服务。此 uddi 注册表是一个内部 uddi 服务器,公开此 web 服务没有什么意义。microsoft 通过 microsoft® windows® .net server 提供本地 uddi 服务。有关此功能的详细信息,请参阅 windows .net server(英文)web 站点。如果没有安装 microsoft .net server,您可以使用 microsoft uddi 软件开发包 (sdk)(英文)在本地计算机上安装 uddi。
可以通过两种方式在 uddi 中注册 web 服务:可以使用 web 用户界面注册,或者使用 uddi sdk 通过编程方式注册 web 服务。sdk 使用起来非常方便,您可以参阅使用 uddi 的 web 服务说明和发现专栏中发布的代码示例。无论使用哪一种方法,都需要先将 web 服务的 wsdl 文件注册为 tmodel。uddi tmodel 是 xml 实体,用于表示接口和抽象的元数据,因此,wsdl 文件表示为 tmodel。然后,您需要将 web 服务的入口点注册为 bindingtemplate。uddi bindingtemplate 是 xml 结构,用于表示有关给定 web 服务的实现细节。有关 uddi 架构及其与 wsdl 的关系的详细信息,请参阅 http://www.uddi.org(英文)和 uddi“最佳实践”文档 using wsdl in a uddi registry 1.05(英文)。
以下是使用 uddi 服务完成上述步骤后得到的 uddi bindingtemplate 结构的示例。请注意,servicekey、bindingkey 和 tmodelkey 都是由 uddi 生成的,并且对于我们保存的实体来说是唯一的。由其他 uddi 注册表生成的关键字会有所不同。<bindingtemplate servicekey="ef25102d-2171-454c-ade9-3dd7a4a914ee" bindingkey="f46fced9-2b8a-4817-b957-f8d8aca0a2f9"> <accesspoint urltype="http"> http://localhost/salesreportusa/salesreport.asmx </accesspoint> <tmodelinstancedetails> <tmodelinstanceinfo tmodelkey= "uuid:b28fe40a-ea62-4657-88d5-752d8a6cdf77" /> </tmodelinstancedetails></bindingtemplate>
在上述结构中,我们突出显示了此 web 服务的入口点和 bindingkey。这对于客户了解这两部分的信息非常重要。此外,如果某个客户端需要获取此 web 服务的 wsdl,则可以使用 tmodelkey 查询 uddi 以获取该 tmodel。使用 web 服务:c# windows 窗体 .net 客户端
现在,我们可以转换一下角色,看看应用程序的客户端部分。在设计时,我们也许可以在 uddi 中找到此 web 服务。我们将下载相应的 wsdl 文件并使用 microsoft visual studio® .net 的 add web reference(添加 web 引用)或 wsdl.exe 生成一个代理类。(wsdl.exe 是一个命令行工具,属于 microsoft .net 框架 sdk 的一部分。)
现在可以开始编写客户端应用程序中的逻辑。在本例中,它是称为 salesreportclient.exe 的 c# windows 窗体应用程序,允许用户查询销售报表信息。
首先,需要将 uddi .net sdk 类添加到项目中,可以下载这些类。(microsoft uddi sdk 版本 1.5.2 [英文] 与 visual studio .net beta 2 兼容;microsoft uddi .net sdk beta 版本 1.75 [英文] 与 visual studio .net release candidate 兼容。) using 声明如下:using system;using system.drawing;using system.collections;using system.componentmodel;using system.configuration;using system.windows.forms;using system.data;using microsoft.uddi;using microsoft.uddi.binding;
然后,需要将 uddi 服务器的入口点存储在此 web 服务所在的位置(毕竟,uddi 本身就是一个 web 服务)。要执行此操作,需要为此 .exe 可执行文件创建一个应用程序配置文件,用于存储 uddi 服务器的位置。web 服务的 bindingkey 也将存储在此配置文件中。通过在 .net 中使用 xml 配置文件,您可以添加任意数量的 appsetting,应用程序可以通过集合获取。<?xml version="1.0" encoding="utf-8" ?><configuration> <appsettings> <add key="uddi_url" value="http://localhost/uddi/api/inquire.asmx" /> <add key="bindingkey" value="f46fced9-2b8a-4817-b957-f8d8aca0a2f9" /></appsettings></configuration>
在此示例中,我们指向驻留在自己的计算机上的 microsoft uddi developer edition 服务器。uddi_url 也可以是公共 uddi 节点之一,或者是驻留在企业内部的 uddi 注册表。使用配置文件 app.config 的命名规则来保存此文件。编译应用程序后,配置文件将被放在 /bin 目录中,并使用它自己的 .exe 名称命名。
刚刚完成的这个步骤(添加有关 web 服务的配置信息),与 visual studio .net 如何在添加到项目的每个 web 引用上公布 url behavior 属性并没有什么不同。通过将该属性更改为 dynamic,visual studio .net 可以创建包含 web 服务入口点的配置文件。上述操作通过提供在运行时重新查询 uddi 的功能,进一步扩展了此概念。因此,配置文件包含了 uddi 节点的入口点和 web 服务的 bindingkey。
现在,我们可以开始对应用程序本身进行编码。首先需要创建一个文本框、一个标签、一个按钮和两个日期时间选择器。然后建立一些全局变量: //应用程序的一些变量 private string inquiryurl = null; private string bindingkey = null; private string accesspoint = null; private bindingtemplate bt; private double salesfigure = 0;
窗体被实例化以后,我们需要初始化以下变量: public form1() { // //windows 窗体设计器支持所必需 // initializecomponent(); //从配置文件导入变量 inquiryurl = configurationsettings.appsettings["uddi_url"]; bindingkey = configurationsettings.appsettings["bindingkey"]; bool initcache = refreshcachefromuddi(); if ( initcache == true ) accesspoint = bt.accesspoint.text; }
refreshcachefromuddi() 函数用于查询 uddi 服务器以查找入口点。使用 uddi sdk 执行 uddi api 调用 (getbindingdetail),将 bindingkey 作为参数传递。 private bool refreshcachefromuddi() { //使用 uddi sdk,设置 uddi 入口点 inquire.url = inquiryurl; //创建 get_bindingdetail uddi api 消息 getbindingdetail gbd = new getbindingdetail(); //添加 bindingkey gbd.bindingkeys.add( bindingkey ); try { bindingdetail bd = gbd.send(); //如果成功,则使用返回集合中的第一个模板 //更新 bindingtemplate 对象 bt = bd.bindingtemplates[0]; return true; } catch (exception err) { textbox1.text += err.message; return false; } }
在应用程序运行期间,我们将入口点的位置放在变量中。如果用户要重新启动应用程序,它将在 uddi 中重新查询入口点,因此应用程序始终拥有对 web 服务的最新更改。如果需要,可以将这些数据缓存在文件系统或数据库中。
接下来,需要创建调用 web 服务自身的函数: private bool invokewebservice() { localhost.salesreport sr = new localhost.salesreport(); //为代理类设置入口点 sr.url = accesspoint; try { salesfigure = sr.getsalestotalbyrange( datetimepicker1.value, datetimepicker2.value ); label1.text = "选定日期的销售图表:$" + salesfigure.tostring(); textbox1.text += "web 服务调用成功!"; return true; } catch (exception err) { textbox1.text += err.message; return false; } }
最后,用户单击按钮时,应用程序将尝试调用 web 服务。 private void button1_click(object sender, system.eventargs e) { cursor.current = cursors.waitcursor; //尝试调用 web 服务 bool webservicesuccess = invokewebservice(); //如果失败,则查询 uddi if ( webservicesuccess == false ) { textbox1.text += "web 服务失败。重新查询 uddi 以获取新的入口点。/n/n"; bool uddisuccess = refreshcachefromuddi(); //重新查询 uddi 成功, if ( uddisuccess == true ) { //将原入口点与新入口点进行比较 //确定是否有所变化 if ( accesspoint.equals( bt.accesspoint.text ) == false) { //如果入口点不同,则一定是新的 //重设变量 accesspoint = bt.accesspoint.text; //并尝试再次调用 web 服务 webservicesuccess = invokewebservice(); //无法使用新信息调用 web 服务 new info if ( webservicesuccess == false ) { textbox1.text += "web 服务再次失败。uddi 中已更新 的入口点无效!/n/n"; } } else { textbox1.text += "uddi 未提供新信息。/n/n"; } } else { textbox1.text += "uddi 刷新失败。/n/n"; } } }
请注意如何在运行时为 web 服务代理类设置入口点。因为所有代理类都是从 system.web.services.protocols.soaphttpclientprotocol 中衍生出来的,所以代理类会公布一系列属性,.url 属性就是其中之一。设置此属性使我们能够在运行时指定入口点。然后,我们可以通过线路发送 soap 请求。如果没有发生异常,则表示一切正常并且从 web 服务返回的数据显示在窗体中。但如果确实发生异常,此函数返回“假”,调用代码将尝试从 uddi 刷新入口点,以重新使用 refreshcachefromuddi() 函数。
重新查询 uddi 之后,我们会将 uddi 返回的入口点与原入口点进行比较。如果入口点相同,则提供者尚未使用新信息更新 uddi,我们所能做的只有尝试与 web 服务的提供者联系,告诉他们 web 服务不响应。但是,如果从 uddi 检索到的入口点不同,则可以尝试再次调用 web 服务。
为模拟故障,可以更改 web 服务的名称。尝试运行应用程序。然后,用 web 服务的新名称更新 uddi 项。再次运行应用程序。应用程序将在 uddi 中找到新的入口点,成功查询新服务,然后保存此信息。如果您完全关闭应用程序,再重新打开,则第一次尝试时应该能够再次调用 web 服务。其他方案
这个“失败时重试”示例讨论的是 uddi 可在运行时用作 web 服务客户端的支持基础结构。在以后的专栏中,我们将讨论其他方案,包括: - 优化入口点查找 - 可能有多个 web 服务支持驻留在不同服务器上的公共接口,这些服务器又位于不同的物理位置。客户端应使用最近的 web 服务。通过运行时 uddi 查找,客户可以根据不同服务的地理分类或与该实现关联的其他元数据来确定最佳入口点。
- 基于公共接口聚合数据 - wsdl 中可能定义了一种用于搜索目录的标准 web 服务接口。该行业的很多供应商都可以实现 web 服务接口,并在 uddi 中发布入口点。在运行时,客户端应用程序可以动态搜索这些入口点并发出查询以收集这种编录数据。通过这种方式,轮询应用程序可以利用运行时 uddi 数据。
此外,我们还将讨论如何优化 wsdl 文件,使其真正充当接口说明文件。总结
uddi 提供了重要的运行时功能,可以集成到应用程序中以创建更强壮的动态客户端。通过将 uddi 用作 web 服务体系结构中的基础结构,可以编写更加可靠的应用程序。