在建立商業網站的時候,開發人員遇到的一種限制是只能把瀏覽器作為使用者介面。例如,在很多情形中,使用者希望在執行某些操作(例如輸入僱員編號)之後從伺服器檢索到資訊。為了達到這個目的,他們將把頁面發回到伺服器,檢索僱員資訊,並用從伺服器上檢索到的資訊重新整理頁面。儘管目前這種重新整理整個頁面的方法很普遍,但是它的效率很低,因為Web頁面重新整理了,並且重新呈現了整個頁面的內容,即使頁面只要少量的部分真正地發生了改變。在搜尋某個類別或者搜尋引擎的時候你就可以注意到這種低下的效率。它的延遲和資源浪費非常明顯。但是,如果相同的功能不用重新整理瀏覽器頁面就能夠完成,使用者體驗將會得到很大的提高。為了實現這種目的,我們需要在不離開當前頁面的情況下執行一段伺服器代碼的途徑,這就是Web服務行為起的作用。在這種情形下,伺服器上執行的程式碼片段是Web服務方法的代碼,瀏覽器的角色是調用這段伺服器代碼而不離開或重新整理當前頁面。
使用Web服務行為的時候,你只需要從某個用戶端瀏覽器的Web頁面中發送一個請求執行特定的Web服務方法。在伺服器端,ASP.NET運行時接收到請求,使用相關的參數調用Web服務方法。在Web服務執行完成後,它把結果傳達給調用者,接下來結果被瀏覽器顯示或處理。其結果是,你可以建立典型的用戶端/伺服器通訊,而不需要理會下層HTTP協議的無狀態(stateless)特性。Web服務行為的另一個優點是為了實現功能,用戶端上只需要一個檔案(webservice.htc)存在。使用Web服務方法的時候,你還可以非同步呼叫Web服務方法。這種能力非常強大,可以用於在用戶端建立豐富的使用者體驗。例如,當使用者繼續處理相同頁面上的事務的時候,你可以使用Web服務行為讓伺服器驗證某些資料。一旦函數調用返回了,你就可以得到執行結果並把結果傳達給使用者。
Web服務行為
Web服務行為是把HTML組件(HTC)檔案作為附屬行為實現功能的,它可以用於Internet Explorer 5及以後版本。前面提到過,Web服務行為通過利用工業標準協議(例如HTTP、SOAP和XML)提供了跨平台叫用遠程Web方法的途徑。Web服務行為的重要特性之一是,它允許你在沒有深厚的SOAP知識的情形下使用這些功能。Web服務通過處理瀏覽器和Web服務之間的SOAP資料包通訊,基本上簡化了Web服務的遠程調用。你不用擔心SOAP訊息的聚集(assembling)和分解(disassembling)。所有處理SOAP詳細資料的代碼都被封裝在行為之中,簡化了主Web頁面中的用戶端指令碼。
Web服務行為是使用特定的IE行為文法嵌入Web頁面的JavaScript檔案。通過把屬性和方法暴露給用戶端指令碼,Web服務行為聚集訊息並分解Web服務發回的響應資訊。行為所暴露的對象不僅能夠啟動清晰的錯誤處理方法,而且提供了對返回資料的簡單地訪問。Web服務行為從用戶端指令碼接收到方法調用,並使用SOAP訊息給Web服務發送請求。結果會返回用戶端指令碼,並且處理過程繼續。接下來Web頁面可以把資訊用於任何需要的情形中,例如更新頁面的某些部分,發送錯誤訊息等等。
Web服務行為的一個關鍵特性是它允許用戶端指令碼訪問Web服務而不用導航到另一個URL。下面的列表詳細說明了Web服務行為支援的重要的方法:
· createUseOptions(建立使用的選項)--允許我們跨越遠程方法調用儲存使用者認證資訊。當我們使用SSL與遠程Web服務通訊的時候會很有用。
· callService(調用服務)--允許我們非同步呼叫遠程Web服務。
· useService(使用服務)--允許我們在調用Web服務的時候為該服務建立一個"友好的"名稱。
為了在IE 5.0和以上版本的Web頁面中使用行為,你必須下載webservice.htc行為檔案,並把它儲存在與你的Web頁面相同的檔案夾中。這個檔案可以從下面的連結下載得到:http://msdn.microsoft.com/downloads/samples/internet/behaviors/library/webservice/webservice.htc。
實現過程
你已經瞭解了Web服務行為的一些基礎知識,現在可以看一個應用程式範例了,它示範了在ASP.NET應用程式中如何使用Web服務行為。在這個例子中,你將建立一個簡單的應用程式,它允許你從Northwind資料庫檢索僱員資訊。應用程式範例還允許基於僱員的ID搜尋僱員資訊。
Employee Web服務的建立過程
在這一部分,你需要首先建立一個叫作EmployeeWebService的新Visual C# Web服務項目。項目建立之後,你需要把預設的Web服務類的名字Service1改成EmployeeService。接著你需要匯入下面的名字空間以執行資料訪問和處理XML資料。
using System.Data.SqlClient;
using System.Xml;
[WebMethod]
public XmlDocument GetEmpDetailsByEmpID (int employeeID)
{
string connString =
System.Configuration.ConfigurationSettings.AppSettings["connectionString"];
SqlConnection sqlConnection = new SqlConnection(connString);
try
{
DataSet employeeDataset = new DataSet("EmployeesRoot");
//把需要執行的預存程序的名字和SqlConnection 對象作為參數傳遞進來
SqlDataAdapter adapter = new SqlDataAdapter();
SqlCommand command = new SqlCommand("Select * from Employees Where EmployeeID ="+ employeeID.ToString(),sqlConnection);
//設定SqlCommand對象的屬性
command.CommandType = CommandType.Text;
adapter.SelectCommand = command;
//使用預存程序返回的值填充資料集
adapter.Fill(employeeDataset,"Employees" );
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(employeeDataset.GetXml());
return xmlDoc;
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (sqlConnection.State == ConnectionState.Open)
{
sqlConnection.Close();
}
}
}
屬性WebMethod表明該方法將作為可以被調用的Web方法暴露。在項目部署的時候,ASP.NET運行時提供使用某些協議(例如XML、HTTP和SOAP)在Internet上調用這個方法所需要的所有管道資訊。
[WebMethod]
上面的方法名稱告訴我們,GetEmpDetailsByEmpID把employeeID作為參數並返回XmlDocument形式的僱員詳細資料。
public XmlDocument GetEmpDetailsByEmpID(int employeeID)
{
string connString = System.Configuration.ConfigurationSettings.
AppSettings["connectionString"];
上面的程式碼使用ConfigurationSettings類的AppSettings屬性從web.config檔案的<appsettings>部分檢索連接字串。在web.config檔案中連接字串是這樣定義的:
<appSettings>
<add key="connectionString"
value="server=localhost;uid=sa;pwd=;database=Northwind" />
</appSettings>
下面一行代碼建立了SqlConnection對象的一個執行個體,給它傳遞了用於建立資料庫連接的連接字串:
SqlConnection sqlConnection = new SqlConnection(connString);
接著你把所有的可執行代碼封裝在一個try...catch代碼塊中以處理執行後面的語句時可能發生的任何錯誤:
try
{
DataSet employeeDataset = new DataSet("EmployeesRoot");
SqlDataAdapter adapter = new SqlDataAdapter();
下一步,你建立了SqlCommand對象的一個執行個體,給它的建構函式傳遞你希望執行的SQL語句和前面步驟中建立的SqlConnection對象:
SqlCommand command = new SqlCommand("Select * from Employees Where EmployeeID =" + employeeID.ToString(),sqlConnection);
接著你把SelectCommand屬性設定為適當的值,表明你希望執行一個SQL語句:
//設定SqlCommand對象的屬性
command.CommandType = CommandType.Text;
接著把SqlDataAdapter對象的SelectCommand屬性設定為前面建立的SqlCommand對象:
adapter.SelectCommand = command;
現在使用Fill方法,通過在資料來源上執行前面指定的SQL語句從資料來源檢索資料:
//用預存程序返回的值填充資料集
adapter.Fill(employeeDataset,"Employees" );
一旦僱員資訊成為資料集形式的,你就可以檢索它的內容,並把它作為參數傳遞到XmlDocument對象的LoadXml方法中。最後,把該XmlDocument對象返回到該Web服務的調用者:
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(employeeDataset.GetXml());
return xmlDoc;
}
catch (Exception ex)
{
throw ex;
}
在最後的代碼塊中,你檢查了State屬性以驗證Connection(串連)是否仍然是開啟的。如果串連仍然是開啟的,你就通過調用該連線物件的Close方法關閉它:
finally
{
if (sqlConnection.State == ConnectionState.Open)
{
sqlConnection.Close();
}
}
}
現在你已經建立了Web服務了,可以用滑鼠右鍵點擊EmployeeService.asmx檔案並選擇Build(建立)和Browse(瀏覽)來測試它的功能。你得到的螢幕應該與下面的類似:
點擊上面的螢幕中的方法名稱(GetEmpDetailsByEmpID)將顯示下面的螢幕:
如果你輸入僱員id並點擊Invoke(調用)來調用該Web服務方法,你將得到下面的輸出:
現在你已經測試了該Web服務了,你應該使用Web服務行為從ASP.NET頁面中調用它來實驗它的功能了。
在ASP.NET頁面中如何使用Web服務行為調用Web服務
在Web頁面中使用Web服務行為的第一步是使用類似下面的文法把它嵌入頁面代碼:
<div id="service" style="BEHAVIOR:url(webservice.htc)"></div>
上面的代碼依賴IE 5(及以上版本)內建的行為功能來驗證JavaScript檔案的位置,而該檔案被用於調用Web服務。前面談到,webservice.htc檔案的位置必須與Web頁面的檔案夾相同。我們要重點注意,行為檔案的載入發生在用戶端而不是伺服器上。
在嵌入了上面的代碼後,我們就可以使用JavaScript代碼調用行為並把它連結到相容WSDL 1.1的Web服務了。這是通過引用被嵌入的行為id(前面代碼中的服務)和調用它的useService方法來實現的:
service.useService("http://localhost/MyProjects/WebServiceBehavior/EmployeeService.asmx?WSDL","svcEmployee");
你需要在頁面的onLoad事件控制代碼中調用useService方法,這樣才能確保在調用Web服務的任何方法之前,該Web服務已經映射了。
UseService方法有下面兩個參數:
· Web服務的WSDL檔案的路徑。
· 用於以後引用該Web服務的一個"友好的"名字。每次使用行為調用EmployeeService.asmx檔案中的方法的時候都會使用這個名字。
現在已經建立了Web服務並可以訪問它了。非同步呼叫Web服務方法可以分為兩個步驟。非同步呼叫的優點是Web頁面不用等待Web服務返回。第一步,你調用Web方法並把回調(callback)函數作為參數。第二步,在執行了需要的方法後,Web服務返回,啟動回呼函數。
下面是完整的原始碼列表:
<%@ Page language="c#" Codebehind="EmployeeServiceClient.aspx.cs"
AutoEventWireup="false" Inherits="
EmployeeWebServiceClient.EmployeeServiceClient" %>
<HTML>
<HEAD>
<title>Employee Details</title>
<SCRIPT LANGUAGE="JScript">
//定義一個模組層級的變數來捕捉事件id
var iCallID ;
function GetEmployeeDetails()
{
// 調用svcEmployee Web服務的GetEmployeeDetails方法
iCallID =
service.svcEmployee.callService(DisplayResults,"GetEmpDetailsByEmpID",txtEmployeeID.value);
}
function DisplayResults(result)
{
var strXML,objXMLNode,objXMLDoc,objEmployee,strHTML;
//檢查事件id是否相同
if (iCallID != result.id)
return;
if(result.error)
{
// 顯示錯誤資訊
var faultCode = result.errorDetail.code;
var faultString = result.errorDetail.string;
alert("ERROR: Code = " + faultCode + ", Fault String=" + faultString);
}
else
{
//把結果值賦予本地變數
objXMLNode = result.value;
objXMLDoc = new ActiveXObject("Microsoft.XMLDOM");
//把返回的XML字串載入XMLDOM對象
objXMLDoc.loadXML(objXMLNode.xml);
//得到Employees節點的指標
objEmployee =
objXMLDoc.selectSingleNode("GetEmpDetailsByEmpIDResult").selectSingleNode("EmployeesRoot").selectSingleNode("Employees");
//檢查從伺服器返回的僱員指標是否有效
strHTML = "<font color=’#0000FF’>";
if (objEmployee != null)
{
//動態產生HTML,並把它添加到一個字串變數中
strHTML += "<br><br>Employee ID :<b>" +
objEmployee.selectSingleNode("EmployeeID").text + "</b><br><br>";
strHTML += "Employee First Name :<b>" +
objEmployee.selectSingleNode("FirstName").text +
"</b><br><br>";
strHTML += "Employee Last Name :<b>" +
objEmployee.selectSingleNode("LastName").text + "</b><br><br>";
strHTML += "Employee Title :<b>" +
objEmployee.selectSingleNode("Title").text + "</b><br><br>";
strHTML += "Employee Title :<b>" +
objEmployee.selectSingleNode("Title").text + "</b><br><br>";
strHTML += "Title Of Courtesy:<b>" +
objEmployee.selectSingleNode("TitleOfCourtesy").text + "</b><br><br>";
strHTML += "Postal Code:<b>" +
objEmployee.selectSingleNode("PostalCode").text + "</b><br><br>";
}
else
{
strHTML += "<br><br><b>Employee
not found</b>";
}
strHTML += "</font>"
//把動態產生的HTML賦予div標記
divContents.innerHTML = strHTML;
}
}
function init()
{
// 建立Web服務的執行個體並把它叫做svcEmployee
service.useService("http://localhost/MyProjects/15Seconds/WebServiceBehavior/_
EmployeeWebService/EmployeeService.asmx?WSDL","svcEmployee");
}
</SCRIPT>
</HEAD>
<body onload="init()">
<div id="service" style="BEHAVIOR: url(webservice.htc)"></div>
<H1 align="center">
<font color="#800080">Employee Details</H1>
</FONT>
<br><br>
<P align="left"><font color="#800080"><b>Enter the
Employee ID:</b></font> <INPUT
id="txtEmployeeID" name="txtEmployeeID" style="LEFT: 149px; TOP:
72px"><INPUT id="btnAdd" type="button" value="Get Employee Details"
name="btnGetEmployee" onclick="return GetEmployeeDetails()"></P><P></P>
<div id="divContents">
</div>
<P></P>
</body>
</HTML>
在GetEmployeeDetails方法中,你通過把回調方法名稱和輸入參數作為參數傳遞給Web服務來調用該Web服務的web方法。這是通過調用Web服務行為的callService方法實現的:
function GetEmployeeDetails()
{
// 調用svcEmployee web服務的GetEmployeeDetails方法
iCallID =
service.svcEmployee.callService(DisplayResults,"GetEmpDetailsByEmpID",txtEmployeeID.value);
}
CallService方法返回一個唯一的標識符,它可以用於識別Web服務調用。如果你進行多個非同步Web服務調用,接著在用戶端瀏覽器中把結果拼裝在一起,那麼這個標識符就是必要的。在這種情況下,你把這個ID與作為result對象的一個屬性返回的ID進行匹配。匹配過程是在回呼函數中完成的:
function DisplayResults(result)
{
var strXML,objXMLNode,objXMLDoc,objEmployee,strHTML;
在下面幾行代碼中,你把result對象的ID與callService方法返回的ID進行匹配:
//檢查事件是否相同
if (iCallID != result.id)
return;
接著檢查error屬性以確定在Web服務的執行過程中是否發生過錯誤。如果發生過錯誤,就在訊息視窗中顯示錯誤資訊。如果沒有錯誤,就處理返回的結果並把它們顯示在HTML DIV標記中:
if(result.error)
{
// 讀取錯誤資訊
var faultCode = result.errorDetail.code;
var faultString = result.errorDetail.string;
alert("ERROR: Code = " + faultCode + ", Fault String=" + faultString);
}
else
{
//把結果值賦予本地變數
objXMLNode = result.value;
objXMLDoc = new ActiveXObject("Microsoft.XMLDOM");
//把返回的XML字串載入XMLDOM 對象
objXMLDoc.loadXML(objXMLNode.xml);
//得到Employees節點的指標
objEmployee = objXMLDoc.selectSingleNode("GetEmpDetailsByEmpIDResult").
selectSingleNode("EmployeesRoot").selectSingleNode("Employees");
//查看從伺服器返回的employee 指標是否有效
strHTML = "<font color=’#0000FF’>";
if (objEmployee != null)
{
//動態產生HTML並添加到字串的內容中
strHTML += "<br><br>Employee ID :<b>" +
objEmployee.selectSingleNode("EmployeeID").text +
"</b><br><br>";
strHTML += "Employee First Name :<b>" +
objEmployee.selectSingleNode("FirstName").text +
"</b><br><br>";
strHTML += "Employee Last Name :<b>" +
objEmployee.selectSingleNode("LastName").text +
"</b><br><br>";
strHTML += "Employee Title :<b>" +
objEmployee.selectSingleNode("Title").text +
"</b><br><br>";
strHTML += "Employee Title :<b>" +
objEmployee.selectSingleNode("Title").text +
"</b><br><br>";
strHTML += "Title Of Courtesy:<b>" +
objEmployee.selectSingleNode("TitleOfCourtesy").text +
"</b><br><br>";
strHTML += "Postal Code:<b>" +
objEmployee.selectSingleNode("PostalCode").text +
"</b><br><br>";
}
else
{
strHTML += "<br><br><b>Employee
not found</b>";
}
strHTML += "</font>"
//把動態產生的HTML賦予div標記
divContents.innerHTML = strHTML;
}
}
在上面的例子中,你使用調用Web服務時指定的回呼函數來處理Web服務返回的結果。還有一種辦法,在定義DIV標記的時候你也可以指定一個回呼函數(用這種方法把Web服務行為包含在頁面中)。例如,在下面的代碼中,你使用Web服務行為支援的onresult事件控制代碼指定了回呼函數:
<div id="service" style="BEHAVIOR: url(webservice.htc)"
onresult="DisplayResults()"></div>
有了上面的定義後,你就可以在DisplayResults函數中處理Web服務返回的結果了。下面的代碼示範了DisplayResults函數的一種實現樣本:
function DisplayResults()
{
//檢查事件id是否相同
if (iCallID != event.result.id)
return;
if(event.result.error)
{
var faultCode = event.result.errorDetail.code;
var faultString = event.result.errorDetail.string;
alert("ERROR: Code = " + faultCode + ", Fault String=" +
faultString);
}
else
{
//顯示結果值
alert(event.result.value);
}
}
在代碼中你可以看到,我們使用event對象得到包含Web服務調用返回結果的result對象的指標。
把代碼放在一起
如果使用瀏覽器查看上面的ASP.NET頁面,你看到的輸出類似。在employee文字框中輸入一個有效Employee ID並點擊"Get Employee Details"調用遠程Web服務。這種操作將導致對該Web服務的非同步呼叫,並且該Web服務返回的結果將顯示在Web頁面的DIV元素中。
我要再次重點強調,對於IE Web服務行為,需要IE 5或以上版本,因此如果你能夠確定使用者所使用的瀏覽器類型,這種技術就很適合用於企業內部網應用程式。
結論
在本文中,你看到了Web服務行為是如何提供一種從Web伺服器向用戶端瀏覽器傳遞資訊的改進的解決方案的。使用Web服務行為調用遠程Web方法簡化了用戶端的操作,使Web服務的使用更加有吸引力。我們同時看到Web服務行為是如何通過提供動態互動操作Web頁面,協助我們提高了使用者體驗的。由於Web服務行為(webservice.htc檔案)封裝了使用SOAP調用遠程Web服務所需要的代碼,隨著SOAP標準的演化,你可以獨立地更新行為而不需要改變用戶端指令碼。