asp.net|指令碼|用戶端 在使用 ASP.NET 的時候,我們仍然在許多情況下需要使用用戶端指令碼。以下是筆者根據自己的經驗和一些粗淺的研究,對此作一個簡要的總結。
一、在 HTML 裡直接寫指令碼
這個方法是最簡單的,直到如今我寫網頁的時候也幾乎還是使用最多的一種方式。也許一些經常使用 RegisterClientScriptBlock 的人會覺得這種方法老土,不過在我看來,它除了可以減少編譯時間以外,更主要的是可以減少代碼量,可讀性也要好一些,更或許還可以避免一些潛在的錯誤。
但是有些情況下,直接寫的方法是很難完成要求的,比如說當指令碼需要依賴我們在代碼中動態產生的控制項時,就必須要採用 Register 的方法了。
二、使用 Literal 控制項寫指令碼
Literal 控制項基本上就是寫一段文字或代碼,所以當然也可以寫出用戶端指令碼來,實質上這個方法與第一個方法基本相同。以我個人來說,此種方法一般是用在寫控制項上。設想一個情況:我們需要根據某種條件來判斷一批具有用戶端指令碼事件的控制項是否顯示,當然我們可以使用如 Panel 一類的容器,可是這些容器多半都要套一個 div 之類的東西,如果在某種情況下我們不希望這時出現 div 呢?當然或許是有更好更漂亮的辦法,只是我都是簡單地用一個 Literal 了事。'
三、使用 Register Script 函數
在 ASP.NET 1.x 中,Page 類提供了 RegisterClientScriptBlock、RegisterOnSubmitStatement 以及 RegisterStartupScript 函數,使得可以在代碼中進行 ClientScript 綁定。不過到了 ASP.NET 2.0 當中,將這些函數統通移到了一個 ClientScriptManager 類中,並且還添加了類似的 RegisterClientScriptInclude 函數。在使用這個類的時候,我們可以通過 Page.ClientScript 來訪問。下面介紹一下這幾個函數的區別。
RegisterClientScriptBlock:這個函數將 ClientScript 加到頁面頂部,一般是在緊接著 <form> 標籤的地方。需要注意的是,這個函數中的指令碼在寫入頁面甚至執行的時候,頁面的其它元素可能還沒有載入完畢,因此可能不能正確地調用頁面中的元素。這個函數比較常見的應用是一些用戶端事件處理函數。
RegisterClientScriptInclude:這個函數是 ASP.NET 2.0 新加入的,它一般用於將一個外部的 ClientScript 檔案,比如一個 .js 檔案連結入頁面的時候。除此之外,它和 RegisterClientScriptBlock 基本一樣,包括注意事項。
RegisterStartupScript:這個函數將 ClientScript 加入到頁面的底部,由此帶來的好處自然就是可以正確地處理對頁面元素的引用了。注意這裡的指令碼將會在頁面的 onload 事件之前執行。一般來說,這裡的代碼都是一次性執行的。最常見的比如說是希望頁面載入完畢之後彈出一個訊息提示框,又或者在一個有架構的頁面中,需要在一個頁面裝載完畢之後更新另一個頁面的情況。
RegisterOnSubmitStatement:這個函數則是將 ClientScript 與 onsubmit 事件綁定起來。
一般的,在使用 RegisterClientScriptBlock 定義了事件處理函數後,我們可以採用以下的代碼將函數與控制項關聯起來,這裡,假定我們已經定義了一個 confirmDelete 函數:
1string script = @"return confirmDelete();";
2btnDelete.Attributes.Add("onclick", script);
四、關於 RegisterClientScriptResource
除了前一節提過的四個函數外,ASP.NET 2.0 還增加了一個 RegisterClientScriptResource 函數。這個函數與前幾個的差異在於:它聯結的是經過編譯成資源的指令碼。比如如下一行
1Page.ClientScript.RegisterClientScriptResource(this.GetType(), "script_resource.js");
這裡的 script_resource.js 必須是在服務端被編譯進 assembly 中去,其方法是在服務端代碼中添加如下一行
1[assembly: WebResource("script_resource.js", "application/x-javascript")]
如此一來之後,在產生的頁面中,我們可以看到類似下面的代碼(為了節省版面,我刪減了 d 和 t 的長度):
1<script src="/webclient/WebResource.axd?d=oZ35V30&t=63204" type="text/javascript"></script>
這裡的 WebResource.axd 的請求被送到服務端之後,由一個特定的 axd HttpHandler 來處理以取得相關的資源(這裡就是 script-resource.js 檔案)。在後面我們還可以看到,這個技術還有著更廣泛的應用。
五、GetPostBackClientHyperlink 與 GetPostBackEventReference
這兩個函數都可以取得一個字串,它可以用來作為用戶端向服務端提交 PostBack 之用。總體上兩者的用途是一樣的,主要區別在於:前者返回的串以 "javascript:" 打頭,而後者則沒有。看以下的例子:
1ClientScriptManager cs = Page.ClientScript;
2btnDelete2.Attributes.Add("onclick", cs.GetPostBackEventReference(btnDelete, btnDelete2.ID.ToString());
3linkDelete.HRef = cs.GetPostBackClientHyperlink(btnDelete, linkDelete.ID.ToString());
看看這一段代碼產生的相關 HTML 源碼:
1<input name="btnDelete2" type="button" id="btnDelete2" value="Delete2" onclick="__doPostBack('btnDelete','btnDelete2')" />
2<a href="javascript:__doPostBack('btnDelete', 'linkDelete')" id="linkDelete">Delete</a>
從這個例子我們可以看到兩個函數的使用,對於超連結的 HRef 來說,應當採用 GetPostBackClientHyperlink,否則瀏覽器可能不能正確地執行。
六、通過用戶端觸發服務端事件
有一些情況下,我們需要通過用戶端的事件來觸發服務端事件。比如說,我們想在一個 TextBox 的 onchange 事件中加一個檢測,如果遇上了使用者輸入斷行符號,那麼就觸發一個 btnGo 的服務端事件。(當然實際上這個例子我們也可以完全用 javascript 辦到,這裡僅作說明)。能辦到嗎?以下是一段代碼:
1string sCommand = Page.ClientScript.GetPostBackClientHyperlink(btnGo, "");
2string script = @"javascript:keyClick(""EVAL_COMMAND"")";
3script = script.Replace("EVAL_COMMAND", strCommand);
4txtSearch.Attributes.Add("onkeydown", script);
以下的相關的 javascript 代碼:
1public partial class CallbackPage : System.Web.UI.ICallbackEventHandler
該介面是 ASP.NET 2.0 新加入的。接下來我們要實現它的兩個成員方法(在我的本機 MSDN 裡,有一些地方使用此介面的成員方法與實際成員不符,比如 RaiseCallbackEvent 的傳回型別變成了 string 且沒有 GetCallbackResult 方法,我估計是早期寫好的但後來沒有更新,大家看的時候要注意):
1public int nCount = 0;
2
3public void RaiseCallbackEvent(String eventArgument)
4{
5 nCount = Convert.ToInt32(eventArgument) + 1;
6}
7
8public string GetCallbackResult()
9{
10 return nCount.ToString();
11}
我們先寫好接收回呼函數的方法,用 javascript 寫:
1function ReceiveServerData(rvalue, context)
2{
3 labelResult.innerText = rvalue;
4}
好,隨後我們需要將 Callback 鏈入頁面,注意,關鍵區段到了:
1void Page_Load(object sender, EventArgs e)
2{
3 ClientScriptManager cs = Page.ClientScript;
4 String cbReference = cs.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context");
5 String callbackScript = "function CallServer(arg, context) {" + cbReference + ";}";
6 cs.RegisterClientScriptBlock(this.GetType(), "CallServer", callbackScript, true);
7}
最後,還需要寫好調用的地方:
1<input type="button" value="TestCallback" onclick="CallServer(value, alert('資料遞增!'))" />
這裡的 value 是需要實現遞增的資料。注意遞增過程是在 RaiseCallbackEvent 函數內完成的,它就相當於 Ajax 中加上了 AjaxMethodAttribute 的函數。運行一下測試,我們可以發現確實也實現了類似 Ajax 的無重新整理頁面!
看看產生的 HTML 源碼,我們可以看到一行 javascript 指令碼資源連結,也是 "WebResource.axd?" 後跟一串參數的一個請求,如同之前講過的 RegisterClientScriptResource 產生的結果一樣。此外還可以看到頁面最後有一個用 RegisterStartupScript 產生的一段:
1<script type="text/javascript"><!--
2WebForm_InitCallback();// -->
3</script>
而 CallServer 函數則被擴充成了類似下面的樣子:
1function CallServer(arg, context)
2{
3 WebForm_DoCallback('__Page',arg,ReceiveServerData,"",null,false);
4}
這裡的 WebForm_InitCallback 和 WebForm_DoCallback 顯然是在 WebRequest.Axd 的請求所產生的 javascript 檔案裡,如果我們從 Temporary Internet Files 裡開啟它,可以看到這兩個函數的實現,細細研究一番,能夠發現它還是使用了 XmlHttpRequest 和 IFrame 來實現的。有興趣的朋友們,去研究吧。
1function keyClick(cmd)
2{
3 if (event.keyCode == 13) {
4 eval(cmd);
5 }
6}
好,根據前面所講的 GetPostBackClientHyperlink 的功能,我們很容易地推斷出第一段代碼所產生的 HTML 源碼:
<input type="text" id="txtSearch" onkeydown="javascript:keyClick("javascript:__doPostBack('btnGo','')");" />
可以看到在 txtSearch 的 onkeydown 處理函數 keyClick 中,通過 eval 函數再次調用了 btnGo 的服務端事件從而實現了由用戶端事件觸發 PostBack。是不是有些奇妙?
七、ASP.NET 中的 Ajax?-- Client Callback
Ajax 技術由於其無重新整理的頁面更新而使許多老式 Web 應用程式顯得極為笨重。並且我們知道有不少 Ajax 其實內部就是 XmlHttpRequest 或是一個 xmlRequestFrame 來實現的,而這兩個 IE 早就支援。那麼微軟有提供基於或是類似於 Ajax 的實現嗎?我知道最近出了一個 Atalas,但其實除此之外,在 ASP.NET 2.0 中就已經有了實作類別似功能的辦法了。
首先,我們必須要使頁面繼承自 ICallbackEventHandler 這個介面。方法可以有如下兩種,分別對應於 Code-Inside 和 Code-Behind 模式:
1<%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %>