本文來自《ASP.NET AJAX程式設計 第II卷:用戶端Microsoft AJAX Library相關》的第三章《非同步呼叫Web Service和頁面中的類方法》,請同時參考本章的其他文章。
3.3 處理非同步呼叫中的異常
在傳統的Web應用程式中,處理異常相對來說比較簡單——即使開發人員不作任何處理,瀏覽器也會預設地將收到的異常資訊顯示在瀏覽器中。而對於Ajax應用程式來說,事情卻並不那麼簡單。Ajax程式“非同步”的天性加上其後台啟動並執行行為,讓使用者乃至開發人員都很難判斷某次對伺服器的調用是否順利完成,瀏覽器自然也對Ajax程式運行時發生的異常無能為力。
在本章前面兩節中,藉助於ASP.NET AJAX非同步通訊層的協助,我們已經能夠容易地從用戶端向伺服器端發起非同步HTTP請求——在理想情況下,這自然不會有什麼問題,也足夠使用。然而,Web程式在運行中會有很多不確定性,從網路狀況的不穩定到開發人員的粗心大意,任何一個難以預料的問題均會導致某次非同步呼叫以失敗告終。
因此,在ASP.NET AJAX非同步通訊層的實現中,自然也內建了對非同步呼叫時異常的處理方法。還記得前面曾經介紹過的在用戶端調用Web Service代理的文法嗎?
[NameSpace].[ClassName].[MethodName](param1, param2 …, callbackFunction)
在調用成功的回呼函數callbackFunction的後面,我們還可以提供另一個調用失敗的回呼函數。這樣,用戶端調用Web Service代理的文法就變為:
[NameSpace].[ClassName].[MethodName](param1, param2 …, onSucceeded, onFailed)
注意其中粗體部分新添加的onFailed回呼函數,該函數將在本次非同步通訊出現異常時由ASP.NET AJAX非同步通訊層調用。而onSucceeded的行為則不會收到任何影響,仍將在成功調用後執行。
onFailed回呼函數將接受一個類型為Sys.Net.WebServiceError的參數,表示異常對象。其函數簽名將類似如下所示:
function onFailed(error) {
// 取得異常資訊並處理
}
ASP.NET AJAX的用戶端Sys.Net.WebServiceError類型封裝了非同步請求伺服器時可能發生異常,它提供了若干個唯讀屬性,提供了對異常資訊的詳細描述。Sys.Net.WebServiceError類型的屬性如表3-1所示。
表3-1 Sys.Net.WebServiceError類型的屬性
- 屬性名稱:描述
- exceptionType:擷取伺服器端異常的具體類型
- message:擷取詳細的異常描述資訊
- statusCode:擷取造成異常的HTTP響應的狀態代碼
- stackTrace:擷取伺服器端異常的堆疊追蹤資訊
- timedOut:擷取一個布爾值,表示異常是否是由於網路連接逾時造成的
注意:根據ASP.NET AJAX用戶端組件的命名規範,訪問屬性均需要在屬性名稱前加上“get_”或“set_”首碼。例如,若想得到某個Sys.Net.WebServiceError類型異常的message屬性值,則應該按照如下方式書寫代碼:
var errorMessage = errorObj.get_message();
下面讓我們用一個簡單的樣本程式示範在用戶端調用Web Service代理時發生異常的處理辦法,以及Sys.Net.WebServiceError類型中各個屬性的使用方法。
該樣本程式是一個除法的計算機,程式將藉助ASP.NET AJAX非同步通訊層將使用者輸入的除數和被除數發送至伺服器,伺服器完成具體的觸發計算過程後再將結果返回至用戶端顯示出來。程式啟動並執行初始介面3-4所示。
圖3-4 除法計算機的初始介面
輸入除數和被除數,然後點擊問號(“?”)按鈕,程式將調用伺服器端Web Service完成本次除法,並將商顯示在問號按鈕中,3-5所示。
圖3-5 執行一次普通的除法
若是使用者輸入的除數為0,那麼顯然伺服器端執行時將拋出異常。我們不會在伺服器端對該異常進行處理,因此將導致本次非同步呼叫失敗,用戶端也會顯示出異常的詳細資料。3-6所示。
圖3-6除數為0導致本次除法失敗
讓我們先從伺服器端的Web Service入手。我們將該Web Service命名為MathService,並在其中定義了一個名為Divide()的方法,用來執行除法操作。Divide()方法所接受的兩個參數分別代表被除數和除數,其邏輯非常簡單,代碼如下:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class MathService : System.Web.Services.WebService
{
[WebMethod]
public int Divide(int a, int b)
{
return (int)(a / b);
}
}
這裡有必要再次提醒一下,Web Service類要添加[ScriptService]屬性,其中需要暴露給用戶端的方法也要添加[WebMethod]屬性——這些都是允許從用戶端調用該Web Service代理的必要條件。
在ASP.NET頁面中,添加ScriptManager控制項以及上述Web Service的引用:
<asp:ScriptManager ID="sm" runat="server">
<Services>
<asp:ServiceReference Path="Services/MathService.asmx" />
</Services>
</asp:ScriptManager>
然後在ASP.NET頁面中定義程式的介面:
<input id="tbA" type="text" style="width: 40px" /> /
<input id="tbB" type="text" style="width: 40px" /> =
<input id="btnInvoke" type="button" value="?"
onclick="return btnInvoke_onclick()" />
<div id="result"></div>
其中前兩個<input />(id分別為tbA和tbB)用來讓使用者輸入被除數和除數;第三個<input />(id為btnInvoke)則作為按鈕(type="button")用來觸發對伺服器端Web Service的調用,並顯示除法完成後的商;下面id為result的<div />用來顯示可能出現的異常資訊。
function btnInvoke_onclick() {
var a = $get("tbA").value;
var b = $get("tbB").value;
MathService.Divide(a, b, onSucceeded, onFailed);
}
注意其中粗體部分,即調用Web Service用戶端代理的一行。其中不但傳入了被除數和除數(a和b),還傳入了成功調用後的回呼函數onSucceeded以及失敗時的回呼函數onFailed。
成功調用時的回呼函數onSucceeded()比較簡單,這裡不贅:
function onSucceeded(result) {
$get("btnInvoke").value = result;
$get("result").innerHTML = "";
}
失敗時的回呼函數onFailed()才是本樣本程式的重點:
function onFailed(error) {
// 取得異常資訊
var stackTrace = error.get_stackTrace();
var message = error.get_message();
var statusCode = error.get_statusCode();
var exceptionType = error.get_exceptionType();
var timeout = error.get_timedOut();
// 顯示異常資訊
$get("result").innerHTML =
"<strong>Stack Trace: </strong>" + stackTrace + "<br/>" +
"<strong>Service Error: </strong>" + message + "<br/>" +
"<strong>Status Code: </strong>" + statusCode + "<br/>" +
"<strong>Exception Type: </strong>" + exceptionType + "<br/>" +
"<strong>Is Timeout: </strong>" + timeout;
$get("btnInvoke").value = "?";
}
可以看到,onFailed()函數首先取得了傳遞進來的Sys.Net.WebServiceError對象的各個屬性,然後再依次顯示到id為result的<div />中。
這樣就完成了本樣本程式的編寫。運行該程式並嘗試做一些除法,若程式編寫正確的話,你將看到3-4、圖3-5和圖3-6所示的介面。
當然,本樣本程式的目的是為了示範調用伺服器端Web Service時發生異常的處理方法,所以自然事無巨悉地將所有異常資訊均顯示了出來。而在實際開發中,我們則不應該完整顯示出此類唐突的異常細節。通常的做法是根據不同的異常進行相應的處理,並在需要的情況下再為使用者顯示出相對友好的提示訊息。