整合分布式應用程序經常是一件非常困難並且錯綜復雜的任務,即使是最富 有經驗的開發者也可能會覺得頭疼。當應用程序在不同的操作系統以及涉及不同 的程序平台時,這個集成問題變得尤其復雜。雖然說,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 XmlDocument
FilterSoapEnvelope(
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.1
User-Agent: Mozilla/4.0 (compatible;
MSIE 6.0; MS Web Services Client Protocol 1.0.2914.16)
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://www.codecraze.com/soap/nntp.php"
Content-Length: 683
Expect: 100-continue
Connection: Keep -Alive
Host: 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:Body
soap: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 OK
Date: Apr, 16 Dec 2002 15:57:47 GMT
Server: Apache/1.3.20 (Unix) ApacheJServ/1.1.2 PHP/
4.0.4pl1 FrontPage/5.0.2.2510 Rewrit/1.1a
X-Powered-By: PHP/4.0.4pl1
Content-Length: 419
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/xml; charset="utf-8"
<?xml version="1.0" encoding="utf-8"?>
<SOAP-ENV:Envelope
xmlns: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 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";
}
}
代碼 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.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;
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}