整合分布式应用程序经常是一件非常困难并且错综复杂的任务
即使是最富有经验的开发者也可能会觉得头疼
当应用程序在不同的操作系统以及涉及不同的程序平台时
这个集成问题变得尤其复杂
虽然说
Web服务承诺可以减轻程序员完成集成任务的困难程度
但是也可能给程序员们带来一些意想不到的麻烦
在这里我们将把一个应用程序和一个PHP Web服务连结起来
以学习一些整合分布式应用程序的方法
以及必要的应对措施
包括运行什么以及不用去做什么
这个Web服务在一个Apache服务器上运行并且使用PHP开发它从各种微软新闻组检索新闻摘要以及它们的关联的文本即使由这个服务提供的数据可以直接使用内部的Net对象存取但是这个服务还是将使用并提供一个连接到非Net平台上的不错的演示我们这里要讨论的实例基于Net beta 版
创建一个Web服务代理
Visual StudioNET提供了一个出色的机制用于自动地生成可用于存取远程Web服务的代理对像因此要首先尝试使用这些函数来导入由PHP服务提供的Web服务描述语言(Web Services Description LanguageWSDL)文件 还可以使用Net SDK的WSDLexe命令行公用程序不幸的是在使用向导导入WSDL之后并不能成功地创建一个代理所以我必须把导入原始的WSDL文件后由VSNet生成的文件转换为WSDL
把模式域名空间从改成 然后清除所有的当WSDL导入过程中由VSNet添加的q域名空间
删除 xmlns:tm=和xmlns: mime= 名字空间因为这个应用程序中不需要包含这些
删除类型元素因为原始的 WSDL文档 并没有包含Web服务的模式信息的指定的元素区段
改变输入输出元素消息属性值为包含tns域名空间前缀的形式:
<portType name=nntpSoapPortType>
<operation name=getheaders parameterOrder=newsgroup numitems>
<input message=tns:getheaders />
<output message=tns:getheadersresponse />
</operation>
<operation name=getarticle parameterOrder=newsgroup article>
<input message=tns:getarticle />
<output message=tns:getarticleresponse />
</operation>
</portType>
在进行了下面的这些微小的改变VSNet向导能够读取WSDL并且自动地生成一个代理在编译了这个代理之后它被包含在一个ASPNET页面中然而当这个ASPNet页面被执行 message does not have a correct SOAP root XML tag这个错误被当作一个SOAP错误从Web服务中返回为了精确地评估这个错误代理调用被一个名为Proxy Trace的公用程序使用以便代理生成SOAP包装这可以通过把下列代码添加进ASPNet页面来实现
msNewsProxy = new SystemNetWebProxy( //localhost:);
在察看了由Net代理生成的SOAP包装之后我有点奇怪为什么会返回这个错误因为实际上一个相对的SOAP包装被生成并被发送到Web服务即使在尝试了好几个转化成代理代码之后这个错误依然持续代码段列表2显示了从PHP Web服务返回的完整的SOAP错误包装
在使用VSNet中创建的代理对象的好几个把ASPNet页面与PHP Web服务连结的不成功的尝试之后我决定从头开始创建SOAP包装以便执行更有效的程序调试{起先它看起来好像由Net代理生成的模式域名空间可能是问题的关键因为Net使用模式规范而PHP服务使用的是版本的规范
然而我把自定义的SOAP包装改为用版本代替版本错误依然存在在尝试了好几个其他的小的改变之后我决定把SOAP包装使用的域名空间前缀和正文元素从soap (由Net代理生成)改为SOAP ENV因为我看见在SOAP错误信息中返回了SOAP ENV前缀(见代码)这表面上看上去微不足道的改变竟解决了问题!当处理任何请求的时候PHP服务显然需要SOAP ENV前缀而拒绝不包含SOAP ENV前缀的要求
创建一个自定义代理
既然已经了解了为什么Web服务返回一个SOAP错误我们就可以创建一个自定义代理来生成网服务期待的SOAP包装虽然创建一个自定义SOAP包装肯定比使用一个由或者WSDLexe公用程序生成的SOAP包装要花更多的时间但是这样做可以完全控制包装的内容为了开始创建自定义代理我创建一个名为msnewsserviceproxy的包含两个字段的新类
public class MSNewsServiceProxy {
string _uri;
string _soapAction;
}
uri字段保存了Web服务的位置而_soapAction字段保存了将要使用SOAP包装发送的SOAPAction数据头的名称在MSNewsServiceProxy类之内添加CreateSoapEnvelope ()SendSoapEnvelope ()和FilterResult ()这三个方法这些方法生成SOAP包装请求把它发送到Web服务然后过滤返回的SOAP包装让我们逐一的看看每个方法注意代码在SOAP包装的根元素上添加一个SOAP ENV域名空间前缀Web服务显然需要这个特定的前缀而拒绝任何不包含这个前缀的信息因为生成的代理发送一个soap域名空间前缀(而不是SOAP ENV)所以它的消息被拒绝Web服务不应该需要一个特定的域名空间前缀而为此拒绝不带此前缀的消息但是域名空间问题也是你必须注意要想使工作更好的完成要执行一些看上去不{>可思议的事情
在SOAP包装被创建之后SendSoapEnvelope ()方法(见代码段)使用了几个SystemNet和SystemIO域名空间中的类来把这个包装发送到Web服务中代码首先通过把_uri变量传送到对象构造器来创建一个HttpWebRequest对象其次与这个请求相关联的相应的MethodContentType和Header都将被发送然后一个StreamWriter对象和HttpWebRequest对象的请求流相关联SOAP包装就被使用StreamWriter的Write ()方法写到流中
从Web服务返回的SOAP包装被HttpWebResponse对象的SendSoapEnvelope ()方法获得
HttpWebResponse response = (HttpWebResponse)requestGetResponse();
如果应答不是空值它将被载入一个XMLTextReaderXMLTextReader被用来填充XmlDocument对象然后从这个方法中返回XmlDocument对象
FilterSoapEnvelope ()方法分析SOAP应答包装并把从Web服务中返回的数据装入自定义代理的消费者使用的XmlDocument对象
private XmlDocument
FilterSoapEnvelope(
XmlDocument doc) {
XmlDocument filterDoc =new XmlDocument();
XmlNode result = docSelectSingleNode(//results);
XmlNode resultImport = filterDocImportNode(resulttrue);
filterDocAppendChild(resultImport);
return filterDoc;
}
虽然过滤器可以使用好几种方法执行但是FilterSoapEnvelope ()方法依靠XPath语句可以在应答SOAP包装中得到结果元素
微软新闻组PHP Web服务展示了允许取得新闻组新闻摘要的两种方法getheaders ()和getmessage () 你可以看到如何在自定义代理类中使用这两种方法(见代码段) 注意每个方法中的代码传递Web服务方法名被调用到CreateSoapEnvelope ()方法和任何使用这个方法关联的参数 在SOAP包装被发送以及应答被接受之后FilterSoapEnvelope ()方法被调用来把返回的数据加载到一个XmlDocument对象中同样这个对象也是代理消费者使用的