使用 XMLHTTP 動態更新網頁

文章翻譯 文章翻譯
文章編號: 893659 - 檢視此文章適用的產品。
ASP.NET Support Voice 專欄

使用 XMLHTTP 動態更新網頁

為了讓這個專欄更符合您的需求,歡迎您提交給我們您感興趣的主題,以及希望在「Microsoft 知識庫」文件和 Support Voice 專欄中看到哪些問題的解決方法。您可以使用 Ask For It 表格提交您的想法和意見反應。在本專欄的底部也有該表格的連結。
全部展開 | 全部摺疊

在此頁中

簡介

我很喜歡從旁觀察妻子如何瀏覽網站,這是研究 Web 應用程式好不好用的方法之ㄧ。她可以在網際網路上到處閒逛而自得其樂,但對於能讓一切各司其職的低階技術層面 (她稱之為「無聊的東西」) 卻幾乎毫無所知。

最近有天晚上,我看到妻子正用某家大企業的電子商務應用程式瀏覽。她當時為了要逐步看到產品列表,使用多重下拉式清單,每選完一個才有資料回傳,提供下一階段的清單讓她選擇。當她逐步從各個下拉式清單中點選項目時,網頁便會回傳以取得下一個下拉式清單的資料。這種經驗令她感到非常沮喪,因為她總覺得回傳的時間實在太久了。

其實應用程式的開發人員只要使用 XMLHTTP 擷取資料代替回傳,就不會讓她這麼沮喪了。這就是本月專欄所要談論的主題。我會告訴您如何使用 XMLHTTP 將網頁的部分內容更新為 Microsoft ASP.NET Web 服務提供的資料,代替回傳的方式。這種作法真的很酷!相信我。

一般性概觀

XMLHTTP 的運作方式是從用戶端傳送要求到 Web 伺服器,而後傳回 XML 資料島。根據接收到的 XML 的結構而定,您可以使用 XSLT 或 XML DOM 來操作,並將網頁的某些部分繫結到該份資料。這是一種功能強大的技術。

附註 Microsoft 已提供一種 Web 服務行為,可讓 Internet Explorer 既快速又容易地對 ASP.NET Web 服務進行非同步呼叫。然而,此行為非但不受支援,況且要以非同步方式更新網頁並不是最好的辦法。您應該改用 XMLHTTP!

在本專欄接下來逐步解說的範例中,我會透過 XMLHTTP 對 ASP.NET Web 服務進行三次 Web 服務呼叫。該 Web 服務將會查詢本端 SQL Server 上的 Northwind 資料庫,並以 XML diffgram 的形式傳回資料集給用戶端。接著我將使用 XML DOM 剖析該份 XML 資料,再據以動態更新網頁的部分內容。完成這整個過程都不會用到回傳的動作。

Web 服務

我所使用的 Web 服務名為 DynaProducts。這是一種基本的 ASP.NET Web 服務,以 C# 撰寫而成,包含下列三個方法。
  • GetCategories – 傳回一個 DataSet,包含這個 Categories 資料表內的所有類別。
  • GetProducts – 傳回一個 DataSet,包含傳送到這個方法的類別下的所有產品。
  • GetProductDetails – 傳回一個 DataSet,包含 ProductID 傳送到這個方法的產品與其詳細資料。

HTML 網頁

這個範例會令您感到驚訝的第一點大概是,透過 ASP.NET Web 服務進行更新的網頁竟然不是 ASP.NET 網頁,而只是一般的 HTML 網頁。不過,我已在頁面上加入了相當多的用戶端 JavaScript,而由這些指令碼會呼叫 Web 服務。

讓我們來看看 HTML 網頁內的第一個程式碼片段。
var objHttp;
var objXmlDoc;

function getDataFromWS(methodName, dataSetName, wsParamValue, wsParamName)
{

    // create the XML object
    objXmlDoc = new ActiveXObject("Msxml2.DOMDocument");

    if (objXmlDoc == null)
    {
        alert("Unable to create DOM document!");
        
    } else {

	    // create an XmlHttp instance
	    objHttp = new ActiveXObject("Microsoft.XMLHTTP");
	
	
	    // Create the SOAP Envelope
	    strEnvelope = "<soap:Envelope xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
	
	            " xsd=\"http://www.w3.org/2001/XMLSchema\"" +
	
	            " soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
	
	            "  <soap:Body>" +
	
	            "    <" + methodName + " xmlns=\"http://jimcoaddins.com/DynaProducts\">" +
	
	            "    </" + methodName + ">" +
	
	            "  </soap:Body>" +
	
	            "</soap:Envelope>";
	
	
	    // Set up the post
	    objHttp.onreadystatechange = function(){
	
	        // a readyState of 4 means we're ready to use the data returned by XMLHTTP
	        if (objHttp.readyState == 4)
	        {
	
	            // get the return envelope
	            var szResponse = objHttp.responseText;
							
	            // load the return into an XML data island
	            objXmlDoc.loadXML(szResponse);
	
	            if (objXmlDoc.parseError.errorCode != 0) {
	                var xmlErr = objXmlDoc.parseError;
	                alert("You have error " + xmlErr.reason);
	            } else {
	
	                switch(dataSetName)
	                {
	                    case "CategoriesDS":
	                        processCategory();
	                        break;
	
	                    case "ProductsDS":
	                        processProducts();
	                        break;
	
	                    case "ProductDetailDS":
	                        processProductDetails();
	                        break;
	
	                }
	            }
	
	        }
	     }
	
	    var szUrl;
	    szUrl = "http://dadatop/wsXmlHttp/DynaProducts.asmx/" + methodName;
	
	    if (wsParamValue != null)
	    {
	
	        szUrl += "?" + wsParamName + "=" + wsParamValue;
	    }
	
	    // send the POST to the Web service
	    objHttp.open("POST", szUrl, true);
	    objHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	    objHttp.send(strEnvelope);
	  }
}
以上是此頁面內最大的程式碼區段,因此我要多說一些細節,讓您瞭解整個來龍去脈。

在這個指令碼區塊的頂端,我建立了兩個變數:objHttpobjXmlDoc。這些變數會用在我的 XMLHTTP 物件和 XML DOM 物件。緊接在後的則是 getDataFromWS 函式的函式定義。此函式將負責對 Web 服務進行用戶端呼叫。函式接受下列四個引數,其中兩個可以自行決定是否使用:
  • methodName – 所要呼叫的 Web 服務方法的名稱。
  • dataSetName – Web 服務所傳回的 DataSet 名稱。
  • wsParamValue – 若有傳遞參數至 Web 服務,即代表該參數的值 (選擇性)。
  • wsParamName – 若有傳遞參數至 Web 服務,即代表該參數的名稱 (選擇性)。
讓我們將 getDataFromWS 函式拆成幾個部分逐一討論。底下是第一部分的程式碼片段:
// create the XML object
    objXmlDoc = new ActiveXObject("Msxml2.DOMDocument");

    if (objXmlDoc == null)
    {
    		alert("Unable to create DOM document!");

    } else {

		// create an XMLHTTP instance
		objHttp = new ActiveXObject("Microsoft.XMLHTTP");
此程式碼區塊會建立 XMLHTTP 物件和 XML 文件物件。接下來,我要開始建立 SOAP 封套。
// Create the SOAP Envelope
strEnvelope = "<soap:Envelope xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
	
          " xsd=\"http://www.w3.org/2001/XMLSchema\"" +
	
          " soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
	
          "  <soap:Body>" +
	
          "    <" + methodName + " xmlns=\"http://jimcoaddins.com/DynaProducts\">" +
	
          "    </" + methodName + ">" +
	
          "  </soap:Body>" +
	
          "</soap:Envelope>";
在這段程式碼中,我將 SOAP 封套指派給字串變數,以便稍後可傳遞該變數至 Web 服務。要知道如何格式化 SOAP 封套,以適用於 Web 服務其實相當容易:您只需瀏覽到 Web 服務,再按一下其中一個方法就能看到該方法的 SOAP 封套。例如,在瀏覽到我用來建立本文的 wsXMLHTTP Web 服務的 GetCategories 方法後,便會看到以下畫面:

摺疊此圖像展開此圖像
envelope.png


ASP.NET 會告訴您如何為 HTTP POST 和 HTTP GET 格式化 SOAP 封套。本文展示的範例將使用 HTTP POST。

很好,接著我們來看下一段程式碼。
// Set up the post
objHttp.onreadystatechange = function(){
	
// a readyState of 4	means we're ready to use the	data returned by	XMLHTTP
	if (objHttp.readyState == 4)
	{
	
		// get	the return envelope
		   var	szResponse	= objHttp.responseText;
	
		   // load	the return into an XML data island
		   objXmlDoc.loadXML(szResponse);
	
		   if (objXmlDoc.parseError.errorCode != 0) {
			var xmlErr =	objXmlDoc.parseError;
				 alert("You have error " + xmlErr.reason);
	}
	else	
	{

		switch(dataSetName)
				{
					case "CategoriesDS":
						processCategory();
						break;
					case "ProductsDS":
						processProducts();
						break;
					case "ProductDetailDS":
					processProductDetails();
						break;

				}
			}
透過 XMLHTTP 提交要求後,XMLHTTP 物件使用 readyState 屬性來追蹤這個要求的狀態。一旦已從 Web 服務接收回所有的資料,readyState 屬性的值即變更為 4。XMLHTTP 物件的 onreadystatechange 屬性可讓您設定回呼函式,以便在 readyState 屬性有所變更時呼叫這個函數。藉由確認資料是否已完整接收,便不致於在資料尚未就緒前就進行處理。

一旦所有的資料都已接收完畢,便使用 responseText 屬性建立含有回應訊息的 XML 資料島。如同您知道的,來自 Web 服務的回應是 XML 格式。在此情況下,傳回的正是 Microsoft ADO.NET 的 DataSet。

此程式碼區塊的下一部分利用 switch 陳述式,根據 Web 服務所傳回的 DataSet 名稱來呼叫適當的函式。稍待片刻我將詳細解說這些函式的程式碼。

現在,讓我們先來看看實際執行 XMLHTTP 要求的程式碼。
var szUrl;
	szUrl = "http://dadatop/wsXmlHttp/DynaProducts.asmx/" + methodName;
	
	if (wsParamValue != null)
	{
	
	      	szUrl += "?" + wsParamName + "=" + wsParamValue;
	}
	
// send the POST to the Web service
	objHttp.open("POST", szUrl, true);
	objHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	objHttp.send(strEnvelope);
為求清楚起見,變數 szUrl 包含了用來呼叫 Web 服務的 URL。接著的 if 陳述式則會檢查是否有任何參數當成 QueryString 值傳遞。您可能想在您的環境中,為 SOAP 封套加上參數,但不管有沒有參數都能正常執行。

再過來是呼叫 XMLHTTP 物件的 open 方法。此處使用了 open 方法的前三個引數:方法、URL 及指定是否進行非同步呼叫的布林值。
重要事項 若您按照此處的作法進行非同步呼叫,請務必透過 onreadystatechanged 屬性設定回呼函式。

設定好內容類型的要求標頭之後,我使用剛才所填妥,代表 SOAP 封套 的字串變數當成要求送出。

用於提交 XMLHTTP 要求的程式碼現在全部介紹完了。接下來的程式碼將說明如何處理瀏覽器這邊的介面,以及如何處理來自 Web 服務呼叫的回應。

首先請看初次載入網頁時呼叫的函式。
function getCategories()
{

  var func = "getDataFromWS('GetCategories', 'CategoriesDS')";
  document.all.lblCategoryDropdown.innerText = 
"Please wait while data is retrieved...";
  window.setTimeout(func, 1);

  }
我對這個函式做的第一件事是建立變數,用來儲存 getDataFromWS 的函式簽名碼。這麼做是因為此函式的結尾處將會呼叫 window.setTimeout,進而呼叫 getDataFromWS 函式。其用意是要讓我能在等候 Web 服務呼叫完成的同時,向使用者顯示當前狀態。請注意 DIV 的 innerText 已變更,顯示訊息指出正在擷取資料。最後則是排定透過 window.setTimeout 呼叫,在 1 毫秒過後執行 getDataFromWS 函式。

處理 Web 服務回應

請回想一下我剛剛使用 onreadystatechanged 屬性設定了回呼函式,也不要忘記該回呼函式還包含 switch 陳述式,根據 DataSet 的名稱來呼叫特定函式。就本例而言,DataSet 的名稱就是 CategoriesDS。因此,回呼函式將會呼叫 processCategory 函式。底下是這個函式的內容,請看它如何利用 XML DOM 剖析 Web 服務的回應。
function processCategory()
{

  // get an XML data island with the category data
  objNodeList = objXmlDoc.getElementsByTagName("Categories");
 
  // add default value to the drop-down
  document.forms[0].drpCategory.options[0] = new Option("Select a Category", 0);

  // walk through the nodeList and populate the drop-down
  for (var i = 0; i < objNodeList.length; i++) 
  {
      var dataNodeList;
      var textNode;
      var valueNode;

      dataNodeList = objNodeList[i].childNodes;
      valueNode = dataNodeList.item(0);
      textNode = dataNodeList.item(1);

      document.forms[0].drpCategory.options[i + 1] = 
new Option(textNode.text, valueNode.text);
      document.all.lblCategoryDropdown.innerText = "Select a Category:";
      document.forms[0].drpCategory.style.visibility = "visible";
       
    }

  }
getDataFromWS 函式已將回應的 XML 載入到 objXmlDoc 物件中。在 processCategory 函式裡,我先取得該 XML 再對其徹底剖析,以逐步將資料填入 Category 下拉式清單。

首先,我使用部分的 XML 回應建立了 IXMLDOMNodeList 物件。經由 Web 服務呼叫所傳回的 DataSet 將以 diffgram 的形式傳回,而在這個回應中,我真正感興趣的只有已經插進 DataSet 中的 DataTable 的資料。只要從含有這個 DataTable 的 XML 區塊來建立 IXMLDOMNodeList 物件,我就能取得這部分的資料。

如果您查看 Web 服務的程式碼,便會發現我已建立一個名為 Categories 的 DataTable 並已將其加入至 DataSet。當 Web 服務傳回 XML 時,資料集將包含在 <CategoriesDS> 區塊內,且 DataTable 中的每一筆資料列將包含在個別的 <Categories> 區塊內,如下列 XML 檔案所示。

您可以從「Microsoft 下載中心」下載下列檔案:
摺疊此圖像展開此圖像
下載
立即下載 GetCategories.xml 套件
摺疊此圖像展開此圖像
下載
立即下載 WSXMLHTTP.exe 套件。 如需有關如何下載 Microsoft 技術支援檔案的詳細資訊,請按一下下面的文件編號,檢視「Microsoft 知識庫」中的文件:
119591 如何從線上服務取得 Microsoft 支援檔案
Microsoft 已對這個檔案做過病毒掃描。Microsoft 是利用發佈當日的最新病毒偵測軟體來掃描檔案,看看有沒有病毒感染。檔案會儲存在安全的伺服器上,以避免任何未經授權的更改。

為了取得含有此 DataTable 的 XML 區塊,我使用下列程式碼:
objNodeList = objXmlDoc.getElementsByTagName("Categories");
這可傳回含有每個 <Categories> 節點的 IXMLDOMNodeList 物件。接著我再使用 for 迴圈反覆檢查該份清單。
// walk through the nodeList and populate the drop-down
  for (var i = 0; i < objNodeList.length; i++) 
  {
      var dataNodeList;
      var textNode;
      var valueNode;

      dataNodeList = objNodeList[i].childNodes;
      valueNode = dataNodeList.item(0);
      textNode = dataNodeList.item(1);

      document.forms[0].drpCategory.options[i + 1] = 
new Option(textNode.text, valueNode.text);
      document.all.lblCategoryDropdown.innerText = "Select a Category:";
      document.forms[0].drpCategory.style.visibility = "visible";
       
    }
我已知道每個 <Categories> 節點會有兩個我所要的節點:<ID> 節點和 <CategoryName> 節點。因此,我所做的第一件事就是建立新的 IXMLDOMNodeList,並將目前 <Categories> 節點的子節點填入其中。
dataNodeList = objNodeList[i].childNodes;
然後我再使用 item 方法,存取需要填入下拉式清單中的那兩個節點。第一個節點含有來自資料庫的 CategoryID 欄位,第二個節點則含有來自資料庫的 CategoryName 欄位。至此,我建立了新的 Option 物件,將其文字設定為 CategoryName,並將其值設定為 CategoryID,然後將該物件加入 drpCategory 下拉式清單中。其餘函式所使用的程式碼也運用同樣的方法,從 XML 回應提取所需的資料,然後填入網頁上的對應部分。

附註 由於本文僅處理少量資料,使用 DOM 提取所需的資料是個不錯的方式。如果是處理大量資料的話,建議您選擇改用 XSLT。

如何整合各部分協同運作

至今我們已討論了所有運作方式的細節,接下來就得靠您取用隨附的範例檔案自行操作一遍觀察實際的執行情形。

部署 Web 服務

如果要部署 ASP.NET Web 服務,只需將隨附的 Web 服務範例解壓縮至您 Web 伺服器的根目錄即可。接著您還必須開啟 DynaProducts.asmx 的程式碼,然後修改連接字串。最低限度,您至少需要輸入 SA 密碼。進行這些變更後,請重新編譯 Web 服務。

部署 HTML 檔案

HTML 檔案中有一個名為 szUrl 的變數,包含 Web 服務的 URL。您可以在 getDataFromWS 函式的底部附近找到該變數。其值必須變更為剛才部署的 Web 服務的 URL。

一旦 Web 服務和 HTML 檔案都已部署完成,請瀏覽這個 HTML 檔案。當網頁載入時,便會透過初次提交至 Web 服務的 XMLHTTP 要求,填入 Category 下拉式清單。填入資料後,從中選取類別,以再次提交 XMLHTTP 要求,並填入 Products 下拉式清單。如果再從 Products 下拉式清單中選取產品,網頁隨即填入表格列出該項產品的相關資料。

請注意,網頁在提交這些 XMLHTTP 要求期間均不曾發生回傳的動作。這正是 XMLHTTP 要求的妙處。如果是在大型網頁上執行,網頁也不會捲動其位置而讓使用者感覺到有任何「閃爍」的現象。在我看來,這真是無與倫比的功能!

最後一提:在本文中,我使用 XMLHTTP 來查詢 Web 服務。我也可以使用 XMLHTTP 來提交 ASPX 網頁或 ASP 網頁的要求。這種技術運用的可能性無限。但願您會發現 XMLHTTP 對您日後的 Web 應用程式開發有很大的幫助。
和往常一樣,請不吝使用 Ask For It 表格,針對您希望能在未來的專欄或「Microsoft 知識庫」中討論的主題,提供您寶貴的意見。

屬性

文章編號: 893659 - 上次校閱: 2006年12月27日 - 版次: 5.2
這篇文章中的資訊適用於:
  • Microsoft ASP.NET 1.0
  • Microsoft ASP.NET 1.1
關鍵字:?
kbgraphic kbscript kbxml kbhowto KB893659
Microsoft及(或)其供應商不就任何在本伺服器上發表的文字資料及其相關圖表資訊的恰當性作任何承諾。所有文字資料及其相關圖表均以「現狀」供應,不負任何擔保責任。Microsoft及(或)其供應商謹此聲明,不負任何對與此資訊有關之擔保責任,包括關於適售性、適用於某一特定用途、權利或不侵權的明示或默示擔保責任。Microsoft及(或)其供應商無論如何不對因或與使用本伺服器上資訊或與資訊的實行有關而引起的契約、過失或其他侵權行為之訴訟中的特別的、間接的、衍生性的損害或任何因使用而喪失所導致的之損害、資料或利潤負任何責任。

提供意見

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com