整合分布式应用程序经常是一件非常困难并且错综复杂的任务,即使是最富有经验的开发者也可能会觉得头疼。当应用程序在不同的操作系统以及涉及不同的程序平台时,这个集成问题变得尤其复杂。虽然说,Web服务承诺可以减轻程序员完成集成任务的困难程度,但是也可能给程序员们带来一些意想不到的麻烦。在这里我们将把一个ASP.Net应用程序和一个PHP Web服务连结起来,以学习一些整合分布式应用程序的方法,以及必要的应对措施,包括运行什么以及不用去做什么。
这个Web服务在一个Apache服务器上运行,并且使用PHP开发。它从各种微软新闻组检索新闻摘要以及它们的关联的文本。即使由这个服务提供的数据可以直接使用内部的.Net对象存取,但是这个服务还是将使用并提供一个连接到非.Net平台上的不错的演示。我们这里要讨论的实例基于.Net beta 2版。
创建一个Web服务代理
Visual Studio.NET提供了一个出色的机制用于自动地生成可用于存取远程Web服务的代理对像。因此,要首先尝试使用这些函数来导入由PHP服务提供的Web服务描述语言(Web Services Description Language,WSDL)文件。 还可以使用.Net SDK的WSDL.exe命令行公用程序。不幸的是,在使用VS.Net向导导入WSDL之后,并不能成功地创建一个代理。所以我必须把导入原始的WSDL文件后由VS.Net生成的文件转换为WSDL:
1. 把模式域名空间从http://www.w3.org/1999/XMLSchema改成http://www.w3.org/2001/XMLSchema 然后清除所有的当WSDL导入过程中由VS.Net添加的”q”域名空间。
2. 删除 xmlns:tm=http://microsoft.com/wsdl/mime/textMatching/和xmlns: mime="http://schemas.xmlsoap.org/wsdl/mime/" 名字空间,因为这个应用程序中不需要包含这些。
3. 删除类型元素,因为原始的 WSDL文档 并没有包含Web服务的模式信息的指定的元素区段。
4. 改变输入输出元素消息属性值为包含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>
在进行了下面的这些微小的改变,VS.Net向导能够读取WSDL并且自动地生成一个代理。在编译了这个代理之后,它被包含在一个ASP.Net页面中。然而,当这个ASP.Net页面被执行:“ message does not have a correct SOAP root XML tag.”,这个错误被当作一个SOAP错误从Web服务中返回。为了精确地评估这个错误,代理调用被一个名为Proxy Trace的公用程序使用,以便代理生成SOAP包装。这可以通过把下列代码添加进ASP.Net页面来实现:
msNews.Proxy = new System.Net.WebProxy( "http://localhost:8080");
在察看了由.Net代理生成的SOAP包装之后,我有点奇怪为什么会返回这个错误,因为实际上一个相对的SOAP包装被生成并被发送到Web服务。即使在尝试了好几个转化成代理代码之后这个错误依然持续。代码段列表2显示了从PHP Web服务返回的完整的SOAP错误包装。
在使用VS.Net中创建的代理对象的好几个把ASP.Net页面与PHP Web服务连结的不成功的尝试之后,我决定从头开始创建SOAP包装以便执行更有效的程序调试。{起先,它看起来好像由.Net代理生成的模式域名空间可能是问题的关键,因为.Net使用2001模式规范而PHP服务使用的是1999版本的规范。
然而,我把自定义的SOAP包装改为用1999版本代替2001版本,错误依然存在。在尝试了好几个其他的小的改变之后,我决定把SOAP包装使用的域名空间前缀和正文元素从soap (由.Net代理生成)改为SOAP - ENV,因为我看见在SOAP错误信息中返回了SOAP - ENV前缀。(见代码2)这表面上看上去微不足道的改变竟解决了问题!当处理任何请求的时候,PHP服务显然需要SOAP - ENV前缀,而拒绝不包含SOAP - ENV前缀的要求。
创建一个自定义代理
既然已经了解了为什么Web服务返回一个SOAP错误,我们就可以创建一个自定义代理来生成网服务期待的SOAP包装。虽然创建一个自定义SOAP包装肯定比使用一个由VS.Net或者WSDL.exe公用程序生成的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服务显然需要这个特定的前缀,而拒绝任何不包含这个前缀的信息。因为VS.Net生成的代理发送一个soap域名空间前缀(而不是SOAP - ENV),所以它的消息被拒绝。Web服务不应该需要一个特定的域名空间前缀而为此拒绝不带此前缀的消息,但是域名空间问题也是你必须注意要想使工作更好的完成,要执行一些看上去不{0>可思议的事情。
在SOAP包装被创建之后,SendSoapEnvelope ()方法(见代码段4)使用了几个System.Net和System.IO域名空间中的类来把这个包装发送到Web服务中。代码首先通过把_uri变量传送到对象构造器来创建一个HttpWebRequest对象。其次,与这个请求相关联的相应的Method,ContentType和Header都将被发送。然后一个StreamWriter对象和HttpWebRequest对象的请求流相关联,SOAP包装就被使用StreamWriter的Write ()方法写到流中。
从Web服务返回的SOAP包装被HttpWebResponse对象的SendSoapEnvelope ()方法获得。
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
如果应答不是空值,它将被载入一个XmlTextReader,XmlTextReader被用来填充XmlDocument对象。然后从这个方法中返回XmlDocument对象。
FilterSoapEnvelope ()方法分析SOAP应答包装并把从Web服务中返回的数据装入自定义代理的“消费者”使用的XmlDocument对象:
private XmlDocumentFilterSoapEnvelope(XmlDocument doc) {XmlDocument filterDoc =new XmlDocument();XmlNode result = doc.SelectSingleNode("//results");XmlNode resultImport = filterDoc.ImportNode(result,true);filterDoc.AppendChild(resultImport);return filterDoc;}
虽然过滤器可以使用好几种方法执行,但是FilterSoapEnvelope ()方法依靠XPath语句可以在应答SOAP包装中得到结果元素。
微软新闻组PHP Web服务展示了允许取得新闻组新闻摘要的两种方法:getheaders ()和getmessage ()。 你可以看到如何在自定义代理类中使用这两种方法(见代码段5)。 注意每个方法中的代码传递Web服务方法名被调用到CreateSoapEnvelope ()方法和任何使用这个方法关联的参数。 在SOAP包装被发送以及应答被接受之后,FilterSoapEnvelope ()方法被调用来把返回的数据加载到一个XmlDocument对象中,同样,这个对象也是代理“消费者”使用的。
整合PHP Web服务
既然一个自定义代理类已经准备好被使用,那么把它整合进一个ASP.Net页面就变得很简单了。getHeaders ()和getMessage ()方法可以调用Web服务,存取返回的XmlDocument对象(见代码段6和7) 在XmlDocument内的子结点中的枚举可以显示这些数据。 虽然许多Web服务可以很容易地使用VS.Net或者WSDL.exe创建的代理类自动地生成,但是仍然会有你需要创建自定义SOAP包装来把.Net和其他的平台整合起来的情况。 本文中介绍的内容以及代码就提供了完成这个整合工作的一种方法。
代码段1:
POST http://www.codecraze.com/soap/nntp.php HTTP/1.1User-Agent: Mozilla/4.0 (compatible;MSIE 6.0; MS Web Services Client Protocol 1.0.2914.16)Content-Type: text/xml; charset=utf-8SOAPAction: "http://www.codecraze.com/soap/nntp.php"Content-Length: 683Expect: 100-continueConnection: Keep-AliveHost: www.codecraze.com<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"xmlns:tns="http://tempuri.org/"xmlns:types="http://tempuri.org/encodedTypes"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Bodysoap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><q1:getheaders xmlns:q1="http://www.codecraze.com/soap/nntp.xsd"><newsgroup xsi:type="xsd:string">microsoft.public.dotnet.xml</newsgroup><numitems xsi:type="xsd:string">15</numitems></q1:getheaders></soap:Body></soap:Envelope>
代码段2:
HTTP/1.1 200 OKDate: Apr, 16 Dec 2002 15:57:47 GMTServer: Apache/1.3.20 (Unix) ApacheJServ/1.1.2 PHP/4.0.4pl1 FrontPage/5.0.2.2510 Rewrit/1.1aX-Powered-By: PHP/4.0.4pl1Content-Length: 419Keep-Alive: timeout=15, max=100Connection: Keep-AliveContent-Type: text/xml; charset="utf-8"<?xml version="1.0" encoding="utf-8"?><SOAP-ENV:Envelopexmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>SOAP-ENV:Client.SOAPMessageFormat</faultcode><faultstring>message does not have a correct SOAP root XML tag</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>
代码段3 :
private string CreateSoapEnvelope(string method,string[] param1,string[] param2) {StringBuilder sb = new StringBuilder();sb.Append("<SOAP-ENV:Envelope");sb.Append(" xmlns:SOAP-" +"ENV="http://schemas.xmlsoap.org/soap/envelope/"");sb.Append(" xmlns:xsi="http://www.w3.org/1999/XMLSchema-" +instance"");sb.Append(" xmlns:xsd="http://www.w3.org/1999/XMLSchema">");sb.Append("<SOAP-ENV:Body>");sb.Append("<m:" + method + " xmlns:m="nntp.xsd">");sb.Append("<" + param1[0] + ">" + param1[1] +"</" + param1[0] + ">");sb.Append("<" + param2[0] + ">" + param2[1] +"</" + param2[0] + ">");sb.Append("</m:" + method + ">");sb.Append("</SOAP-ENV:Body>");sb.Append("</SOAP-ENV:Envelope>");return sb.ToString();}
代码段4:
private XmlDocument SendSoapEnvelope(string soapEnv) {HttpWebRequest request =(HttpWebRequest)WebRequest.Create(_uri);XmlTextReader xmlReader = null;XmlDocument xmlDoc = null;request.Method = "POST";request.ContentType = "text/xml";request.Headers.Add("SOAPAction",_soapAction);StreamWriter writer = new StreamWriter(request.GetRequestStream());writer.Write(soapEnv);writer.Close();HttpWebResponse response =(HttpWebResponse)request.GetResponse();if (response != null) {xmlReader = new XmlTextReader(response.GetResponseStream());xmlDoc = new XmlDocument();xmlDoc.Load(xmlReader);return xmlDoc;} else {return xmlDoc;}}
代码段5:
public XmlDocument getHeaders(string newsgroup,string numitems) {string soapEnv = CreateSoapEnvelope("getheaders",new string[2]{"newsgroup",newsgroup},new string[2]{"numitems",numitems});return FilterSoapEnvelope(SendSoapEnvelope(soapEnv));}public XmlDocument getMessage(string newsgroup,string article) {string soapEnv = CreateSoapEnvelope("getmessage",new string[2]{"newsgroup",newsgroup},new string[2]{"article",article});return FilterSoapEnvelope(SendSoapEnvelope(soapEnv));}
代码段6:
private void Page_Load(object sender, System.EventArgs e){StringBuilder sb = new StringBuilder();try {MSNewsService.MSNewsServiceProxy news =new MSNewsService.MSNewsServiceProxy();XmlDocument newsHeaders =news.getHeaders("microsoft.public.dotnet.xml","15");sb.Append("<ul>");foreach (XmlNode node innewsHeaders.DocumentElement.ChildNodes) {sb.Append("<li><a href="JavaScript:readArticle('" + node.FirstChild.InnerText + "','" +node.ChildNodes.Item(1).InnerText +"')">" + node.ChildNodes.Item(1).InnerText +"</a></li>");}sb.Append("<ul>");lblMessages.Text = sb.ToString();}catch (Exception exp) {lblMessages.Text ="The Web Service appears to be" + " unavailable";}}
代码MSNewsServiceProxy.cs:
using System.Net;using System.Xml;using System.IO;using System.Text;namespace MSNewsService {public class MSNewsServiceProxy {string _uri;string _soapAction;public MSNewsServiceProxy() {_uri = "http://www.codecraze.com/soap/nntp.php";_soapAction = "http://www.codecraze.com/soap/nntp.php";}private XmlDocument SendSoapEnvelope(string soapEnv) {HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_uri);XmlTextReader xmlReader = null;XmlDocument xmlDoc = null;request.Method = "POST";request.ContentType = "text/xml";request.Headers.Add("SOAPAction",_soapAction);StreamWriter writer = new StreamWriter(request.GetRequestStream());writer.Write(soapEnv);writer.Close();HttpWebResponse response = (HttpWebResponse)request.GetResponse();if (response != null) {xmlReader = new XmlTextReader(response.GetResponseStream());xmlDoc = new XmlDocument();xmlDoc.Load(xmlReader);return xmlDoc;} else {return xmlDoc;}}private string CreateSoapEnvelope(string method,string[] param1, string[] param2) {StringBuilder sb = new StringBuilder();sb.Append("<SOAP-ENV:Envelope");sb.Append(" xmlns:SOAP-" +"ENV="http://schemas.xmlsoap.org/soap/envelope/"");sb.Append(" xmlns:xsi="http://www.w3.org/1999/XMLSchema-" +"instance"");sb.Append(" xmlns:xsd="http://www.w3.org/1999/XMLSchema">");sb.Append("<SOAP-ENV:Body>");sb.Append("<m:" + method + " xmlns:m="nntp.xsd">");sb.Append("<" + param1[0] + ">" + param1[1] + "</" + param1[0] + ">");sb.Append("<" + param2[0] + ">" + param2[1] + "</" + param2[0] + ">");sb.Append("</m:" + method + ">");sb.Append("</SOAP-ENV:Body>");sb.Append("</SOAP-ENV:Envelope>");return sb.ToString();}public XmlDocument getHeaders(string newsgroup,string numitems) {string soapEnv = CreateSoapEnvelope("getheaders",new string[2]{"newsgroup",newsgroup},new string[2]{"numitems",numitems});return FilterSoapEnvelope(SendSoapEnvelope(soapEnv));}public XmlDocument getMessage(string newsgroup,string article) {string soapEnv = CreateSoapEnvelope("getmessage",new string[2]{"newsgroup",newsgroup},new string[2]{"article",article});return FilterSoapEnvelope(SendSoapEnvelope(soapEnv));}private XmlDocument FilterSoapEnvelope(XmlDocument doc) {XmlDocument filterDoc = new XmlDocument();XmlNode result = doc.SelectSingleNode("//results");XmlNode resultImport = filterDoc.ImportNode(result,true);filterDoc.AppendChild(resultImport);return filterDoc;}}}
代码msNewsClient.aspx.cs
using System;using System.Collections;using System.ComponentModel;using System.Data;using System.Drawing;using System.Web;using System.Web.SessionState;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls;using System.Xml;using System.Text;namespace MSNewsService{public class MSNewsServiceClient : System.Web.UI.Page{protected System.Web.UI.WebControls.Label lblMessages;public MSNewsServiceClient(){Page.Init += new System.EventHandler(Page_Init);}private void Page_Load(object sender, System.EventArgs e) {StringBuilder sb = new StringBuilder();try {MSNewsService.MSNewsServiceProxy news = new MSNewsService.MSNewsServiceProxy();XmlDocument newsHeaders = news.getHeaders("microsoft.public.dotnet.xml","15");sb.Append("<ul>");foreach (XmlNode node in newsHeaders.DocumentElement.ChildNodes) {sb.Append("<li><a href="JavaScript:readArticle('" + node.FirstChild.InnerText +"','" + node.ChildNodes.Item(1).InnerText +"')">" + node.ChildNodes.Item(1).InnerText + "</a></li>");}sb.Append("<ul>");lblMessages.Text = sb.ToString();}catch (Exception exp) {lblMessages.Text = "The Web Service appears to be unavailable";}}private void Page_Init(object sender, EventArgs e){InitializeComponent();}private void InitializeComponent(){this.Load += new System.EventHandler(this.Page_Load);}}}
代码msNewsMessage.aspx
<%@ Page language="c#" src="msNewsMessage.aspx.cs" Codebehind="msNewsMessage.aspx.cs" AutoEventWireup="false" Inherits="MSNewsService.msNewsMessage" %><HTML><body bgcolor="#ffffff"><h2><asp:Label ID="lblMessageTitle" Runat="server" /></h2><p><asp:Label ID="lblMessageText" Runat="server" /></p></body></HTML>代码msNewsMessage.aspx.csusing System;using System.Collections;using System.ComponentModel;using System.Data;using System.Drawing;using System.Web;using System.Web.SessionState;using System.Web.UI;using System.Web.UI.WebControls;using System.Web.UI.HtmlControls;using System.Xml;namespace MSNewsService{public class msNewsMessage : System.Web.UI.Page{protected System.Web.UI.WebControls.Label lblMessageTitle;protected System.Web.UI.WebControls.Label lblMessageText;public msNewsMessage(){Page.Init += new System.EventHandler(Page_Init);}private void Page_Load(object sender, System.EventArgs e) {try {MSNewsService.MSNewsServiceProxy news = new MSNewsService.MSNewsServiceProxy();XmlDocument newsHeaders = news.getMessage("microsoft.public.dotnet.xml",Request["articleID"]);lblMessageTitle.Text = Request["title"];lblMessageText.Text = newsHeaders.FirstChild.InnerText;}catch (Exception exp) {lblMessageText.Text = "The Web Service appears to be unavailable";}}private void Page_Init(object sender, EventArgs e){InitializeComponent();}private void InitializeComponent(){this.Load += new System.EventHandler(this.Page_Load);}}}
代码msNewsClient.apsx
<%@ Page language="c#" src="msNewsClient.aspx.cs" Codebehind="msNewsClient.aspx.cs" AutoEventWireup="false" Inherits="MSNewsService.MSNewsServiceClient" %><HTML><head><script language="javascript">function readArticle(id,title) {openWindow("msNewsMessage.aspx?articleID=" + id +"&title=" + title,500,400);}function openWindow(page,w,h) {window.open(page,"","status=no,width=" + w +",height=" + h + ",scrollbars=yes");}</script></head><body bgcolor="#ffffff"><h2>Recent posts from: microsoft.public.dotnet.xml</h2><p /><asp:Label ID="lblMessages" Runat="server" /></body></HTML><0}