|

asp.net夜話之五:Page類和回調技術 在今天我主要要介紹的有如下知識點: Page類介紹 Page的生命周期 IsPostBack屬性 ClientScriptManager類 回調技術(CallBack) Page類介紹 asp.net有時候也被成為WebForm,因為開發(fā)一個asp.net頁面就像開發(fā)一個WinFrom窗體一樣,我們同樣可以采用拖拽控件、雙擊產生相關處理代碼的方法。在asp.net中,創(chuàng)建一個頁面可以采用兩種模型。
單頁模型 用Dreamweaver創(chuàng)建的asp.net頁面就是單頁模型,當然利用Visual Studio 2005也能創(chuàng)建單頁模型,不過在Visual Studio 2005中創(chuàng)建的頁面默認不是單頁模型,要想在Visual Studio 2005創(chuàng)建單頁模型的網頁如下:
注意確?!皩⒋a放在單獨的文件中”選項處于未選中狀態(tài),默認情況下這個選項是處于選中狀態(tài)的。這樣就創(chuàng)建了單頁模型的網頁。 此時的頁面代碼如下:
<%@ Page Language='C#' %> <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd'> <script runat='server'> </script> <html xmlns='http://www./1999/xhtml' > <head runat='server'> <title>無標題頁</title> </head> <body> <form id='form1' runat='server'> <div> </div> </form> </body> </html>
注意在頁面中有這樣一句代碼: <script runat='server'> </script>
這句代碼與普通javascript語句塊不同的是有一個runat='server'屬性,表示這里的代碼是在服務器上運行的C#代碼。切換到設計視圖,然后雙擊頁面,然后這部分會變成如下的樣子: <script runat='server'> protected void Page_Load(object sender, EventArgs e) { } </script>
其中Page_Load就是頁面加載的時候在服務器上運行的方法。 單頁模型的特點是HTML標記、控件代碼及服務器端運行的C#代碼全部包含在一個aspx頁面中,Web服務器第一次運行該頁面的時候會將這個頁面生成一個類文件,對于上面的Index.aspx頁面,會生成ASP.Index_aspx的類,然后再將這個ASP.Index_aspx類編譯成IL代碼,Web服務器通過CLR(Common Language Runtime,通用語言運行環(huán)境)運行相應的IL代碼。 單頁模型的缺點是頁面和代碼混在一起,維護起來較為麻煩。 代碼頁面分離模式 代碼頁面模式就是將頁的標記(HTML代碼)和服務器端元素放在.aspx頁面中,而也代碼在位于一個.aspx.cs中。采用默認方式創(chuàng)建的aspx網頁就是這種方式。 下面就是一個采用代碼頁面分離模式創(chuàng)建的Home.aspx頁面的代碼: <%@ Page Language='C#' AutoEventWireup='true' CodeFile='Home.aspx.cs' Inherits='Home' %> <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www./1999/xhtml' > <head runat='server'> <title>無標題頁</title> </head> <body> <form id='form1' runat='server'> <div> </div> </form> </body> </html>
其對應的頁代碼是: using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class Home : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } }
首先要關注的aspx的頭部分代碼: <%@ Page Language='C#' AutoEventWireup='true' CodeFile='Home.aspx.cs' Inherits='Home' %>
@Page是一個頁面指令,在這里Language='C#'指明了當前頁面采用的后臺代碼是C#語言,CodeFile='Home.aspx.cs'表示這個頁面對應的頁代碼文件是Home.aspx.cs這個文件,Inherits='Home'表示當前aspx頁繼承自Home這個類。 現(xiàn)在再關注一下頁代碼文件聲明: public partial class Home : System.Web.UI.Page
從這部分代碼可以看出Home類是繼承自System.Web.UI.Page類的。注意這里還有一個C#2.0的關鍵字partial,這個關鍵字表示當前代碼是一個局部類,以表示這個類是構成整個Web頁面窗體的一部分。Web服務器運行這個頁面的時候最終會將aspx頁面和對應的頁代碼編譯成一個類文件,然后生成IL代碼。 代碼頁面分離模式的好處是頁面展示部分和邏輯控制部分的代碼分離開來,便于管理和維護,這也是微軟推薦的開發(fā)方式。 asp.net頁面的聲明周期 asp.net頁面運行的時候將經歷一個聲明周期,這個生命周期中會進行一系列的操作,調用一系列的方法。了解asp.net頁面的生命周期對于精確控制頁面的控件呈現(xiàn)方式和行為非常重要。 一般說來一個常規(guī)頁面要經歷如下幾個生命周期階段: | 階段 | 說明 | | 頁請求 | 頁請求發(fā)生在頁生命周期開始之前。用戶請求頁時,ASP.NET 將確定是否需要分析和編譯頁(從而開始頁的生命周期),或者是否可以在不運行頁的情況下發(fā)送頁的緩存版本以進行響應。 | | 開始 | 在開始階段,將設置頁屬性,如 Request 和 Response。在此階段,頁還將確定請求是回發(fā)請求還是新請求,并設置 IsPostBack 屬性。此外,在開始階段期間,還將設置頁的 UICulture 屬性。 | | 頁初始化 | 頁初始化期間,可以使用頁中的控件,并將設置每個控件的 UniqueID 屬性。此外,任何主題都將應用于頁。如果當前請求是回發(fā)請求,則回發(fā)數(shù)據尚未加載,并且控件屬性值尚未還原為視圖狀態(tài)中的值。 | | 加載 | 加載期間,如果當前請求是回發(fā)請求,則將使用從視圖狀態(tài)和控件狀態(tài)恢復的信息加載控件屬性。 | | 驗證 | 在驗證期間,將調用所有驗證程序控件的 Validate 方法,此方法將設置各個驗證程序控件和頁的 IsValid 屬性。 | | 回發(fā)事件處理 | 如果請求是回發(fā)請求,則將調用所有事件處理程序。 | | 呈現(xiàn) | 在呈現(xiàn)期間,視圖狀態(tài)將被保存到頁,然后頁將調用每個控件,以將其呈現(xiàn)的輸出提供給頁的 Response 屬性的 OutputStream。 | | 卸載 | 完全呈現(xiàn)頁、將頁發(fā)送至客戶端并準備丟棄時,將調用卸載。此時,將卸載頁屬性(如 Response 和 Request)并執(zhí)行清理。 |
在頁的生命周期中,一般會有如下事件: | 頁事件 | 典型使用 | | Page_PreInit | 使用 IsPostBack 屬性確定是否是第一次處理該頁。 創(chuàng)建或重新創(chuàng)建動態(tài)控件。 動態(tài)設置主控頁。 動態(tài)設置 Theme 屬性。 讀取或設置配置文件屬性值。 注意:如果請求是回發(fā)請求,則控件的值尚未從視圖狀態(tài)還原。如果在此階段設置控件屬性,則其值可能會在下一階段被改寫。 | | Page_Init | 讀取或初始化控件屬性。 | | Page_Load | 讀取和更新控件屬性。 | | Control events | 執(zhí)行特定于應用程序的處理: 如果頁包含驗證程序控件,請在執(zhí)行任何處理之前檢查頁和各個驗證控件的 IsValid 屬性。 處理特定事件,如 Button 控件的 Click 事件。 | | Page_PreRender | 對頁的內容進行最后更改。 | | Page_Unload | 執(zhí)行最后的清理工作,可能包括: 關閉打開的文件和數(shù)據庫連接。 完成日志記錄或其他特定于請求的任務。 |
需要注意的是,每個asp.net控件也有與asp.net類似的生命周期,如果aspx頁面中包含有asp.net服務器控件,那么在調用頁面的方法時也會調用控件的相關方法。另外,Web應用程序是無狀態(tài)的。每次請求一個新網頁或者刷新頁面服務器都會創(chuàng)建一個當前頁的新實例,這就意味著無法獲取頁面的以前的信息,如果確實需要這么做,需要采用額外的機制。 我們將剛才新建的Index.aspx頁面中添加代碼,如下: <%@ Page Language='C#' %> <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd'> <script runat='server'> string date; protected void Page_Load(object sender, EventArgs e) { if (date == null)//如果date為空則設置為當前時間的字符串形式 { date = DateTime.Now.ToString(); } Response.Write('當前時間:'+date); } </script> <html xmlns='http://www./1999/xhtml' > <head runat='server'> <title>無標題頁</title> </head> <body> <form id='form1' runat='server'> <div> </div> </form> </body> </html>
按照正常理解,第一次運行的時候date字符串為null,會被設置成系統(tǒng)當前的字符串表示形式,并且輸出,再次刷新的時候date字符串不再為空,會依然輸出剛才的時間字符串,但是結果卻不是這樣。第一次運行的結果:  刷新頁面之后的結果:  這就證明了即使是刷新當前頁也會重新生成一個當前頁面的實例,因為只有在生成頁面新實例的情況下date字符串變量才為空,才會被重新設置值。 IsPostBack屬性 Page類有一個IsPostBack屬性,這個屬性用來指示當前頁面是第一次加載還是響應了頁面上某個控件的服務器事件導致回發(fā)而加載。 這次我們繼續(xù)對Index.aspx頁面添加代碼,在頁面中增加了一個Button控件,如下: <%@ Page Language='C#' %> <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd'> <script runat='server'> string date; protected void Page_Load(object sender, EventArgs e) { if (date == null)//如果date為空則設置為當前時間的字符串形式 { date = DateTime.Now.ToString(); } Response.Write('當前時間:'+date); if (!Page.IsPostBack) { Response.Write('第一次加載。'); } else { Response.Write('響應客戶端回發(fā)而加載。'); } } protected void btnOK_Click(object sender, EventArgs e) { } </script> <html xmlns='http://www./1999/xhtml' > <head runat='server'> <title>無標題頁</title> </head> <body> <form id='form1' runat='server'> <div> <asp:Button ID='btnOK' runat='server' OnClick='btnOK_Click' Text='提交' /></div> </form> </body> </html>
頁面第一次運行的結果:
按一下F5刷新頁面的結果:
點擊一下“提交”按鈕之后的結果:
由此可見每次打開一個頁面和刷新一個頁面效果都是一樣的,只有響應客戶端回發(fā)時IsPostBack屬性才是true。了解這個屬性和服務器采用了一種機制來“記錄”服務器控件的狀態(tài)這種做法(其實利用了ViewState和ControlState機制,這部分后續(xù)文章中會講到)對于將來數(shù)據綁定會有很大作用。 動態(tài)輸出javascript腳本 對于Index.aspx頁面上面的執(zhí)行情況,我們看到了滿意的結果。我們再來看一下這個頁面在客戶端生成的HTML代碼,在瀏覽器窗口打開的頁面點鼠標右鍵,然后選擇“查看源文件”,HTML代碼如下: 當前時間:2008-9-21 0:04:33響應客戶端回發(fā)而加載。 <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www./1999/xhtml' > <head><title> 無標題頁 </title></head> <body> <form name='form1' method='post' action='Index.aspx' id='form1'> <div> <input type='hidden' name='__VIEWSTATE' id='__VIEWSTATE' value='/wEPDwUKMTY3NzE5MjIyMGRkD2VvJFADDdEHh4W9UfAyzIvI3ss=' /> </div> <div> <input type='submit' name='btnOK' value='提交' id='btnOK' /></div> <div> <input type='hidden' name='__EVENTVALIDATION' id='__EVENTVALIDATION' value='/wEWAgL8vZamCQLdkpmPAeM33vfm1ARVNKdKAoq5+eQdFI1J' /> </div></form> </body> </html>
我們會看到“當前時間:2008-9-21 0:04:33響應客戶端回發(fā)而加載?!边@句話位于<html></html>標記之外。在第一夜時候就提到過,asp.net頁面是滿足XML標準的HTML語言,但是通過在Page_Load事件中利用Response屬性會將文字輸出在<html></html>標記之外,不符合XHTML標準。這對于普通頁面來說也許并無大礙,但是如果在頻繁輸出javascript腳本的網頁中,可能會對網頁的客戶端執(zhí)行效果產生影響。因為javascript腳本塊在客戶端調用方法之前還是客戶端調用方法之后效果可能會不一樣。 下面在Home窗體的Page_Load事件中添加代碼,如下: using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class Home : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { Response.Write('<script language='javascript'>alert('' + DateTime.Now.ToString() + '')</script>'); } } }
這樣每次運行Home.aspx頁面的時候都會彈出一個對話框,如下圖:  這不是我們所關心的,我們關注的是生成的HTML代碼,如下: <script language='javascript'>alert('2008-9-21 0:22:52')</script> <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www./1999/xhtml' > <head><title> 無標題頁 </title></head> <body> <form name='form1' method='post' action='Home.aspx' id='form1'> <div> <input type='hidden' name='__VIEWSTATE' id='__VIEWSTATE' value='/wEPDwUJNzgzNDMwNTMzZGTB6tgIyCoS2q3pZeKmhFwC24pQzw==' /> </div> <div> </div> </form> </body> </html>
可以看見輸出的javascript代碼在<html></html>標記之外。 在Page類中有一個ClientScript屬性,它是ClientScriptManager的實例,這個類是在asp.net2.0中新增的。ClientScriptManager有如下幾個常用方法: RegisterClientScriptBlock方法:向 Page 對象注冊客戶端腳本。 RegisterStartupScript方法:向 Page 對象注冊啟動腳本。 ClientScriptManager類通過鍵string和Type來唯一標識腳本。具有相同類型的鍵和Type的腳本識為同一腳本。 下面對Home窗體的Page_Load事件中輸入如下代碼: using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; public partial class Home : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!ClientScript.IsClientScriptBlockRegistered(this.GetType(), 'ClientScriptBlock')) { ClientScript.RegisterClientScriptBlock(this.GetType(), 'ClientScriptBlock', '<script language='javascript'>alert('ClientScriptBlock')</script>'); } if (!ClientScript.IsStartupScriptRegistered(this.GetType(), 'StartupScript')) { ClientScript.RegisterStartupScript(this.GetType(), 'StartupScript', '<script language='javascript'>alert('StartupScript')</script>'); } //Response.Write('<script language='javascript'>alert('' + DateTime.Now.ToString() + '')</script>'); } }
執(zhí)行該頁面時,會彈出兩個提示窗口,生成的HTML代碼如下: <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www./1999/xhtml' > <head><title> 無標題頁 </title></head> <body> <form name='form1' method='post' action='Home.aspx' id='form1'> <div> <input type='hidden' name='__VIEWSTATE' id='__VIEWSTATE' value='/wEPDwUJNzgzNDMwNTMzZGTB6tgIyCoS2q3pZeKmhFwC24pQzw==' /> </div> <script language='javascript'>alert('ClientScriptBlock')</script> <div> </div> <script language='javascript'>alert('StartupScript')</script></form> </body> </html>
可以看出上面的兩個方法輸出的javascript腳本都在<form></form>標記之內,不會破環(huán)文章的結構,而且RegisterClientScriptBlock方法輸出的javascript腳本代碼塊靠近<form>標記的開始標記,而RegisterStartupScript方法輸出的javascript腳本代碼塊靠近<form>標記的結束標記,了解這一點對于控制動態(tài)添加的客戶端腳本的時間是非常有利的。 回調技術(CallBack) 在asp.net中客戶端與服務器端的交互默認都是整頁面提交,此時客戶端將當前頁面表單中的數(shù)據(包括一些自動生成的隱藏域)都提交到服務器端,服務器重新實例化一個當前頁面類的實例響應這個請求,然后將整個頁面的內容重新發(fā)送到客戶端,這種處理方式對運行結果沒什么影響,不過這種方式加重了網絡的數(shù)據傳輸負擔、加大了服務器的工作壓力,并且用戶還需要等待最終處理結果。假如是我們希望有這么一個功能,當用戶填寫完用戶名之后就檢查服務器數(shù)據庫里是否已存在該用戶名,如果存在就給出已經存在此用戶名的提示,如果不存在就提示用戶此用戶名可用,對于這種情況其實只需要傳遞一個用戶名作為參數(shù)即可,上面的做法卻需要提交整個表單,有點小題大做。解決上面的問題的辦法目前主流做法有三種:純javascript實現(xiàn)、微軟Ajax類庫實現(xiàn)還有用AjaxPro實現(xiàn)。后兩種做法在稍后的文章中會講到,這里我講另外一種實現(xiàn):通過回調技術。 創(chuàng)建實現(xiàn)回調技術的網頁與普通asp.net網頁類似,只不過還需要做以下特殊工作: (1)讓當前頁面實現(xiàn)ICallbackEventHandler接口,這個接口定義了兩個方法:string GetCallbackResult ()方法和void RaiseCallbackEvent (string eventArgument)方法。其中GetCallbackResult ()方法的作用是返回以控件為目標的回調事件的結果,RaiseCallbackEvent()方法的作用是處理以控件為目標的回調事件。 (2)為當前頁提供三個javascript客戶端腳本函數(shù)。一個javascript函數(shù)用于執(zhí)行對服務器的實際請求,在這個函數(shù)中可以提供一個字符串類型的參數(shù)發(fā)送到服務器端;另一個javascript函數(shù)用于接收服務器端方法的執(zhí)行后返回的字符串類型結果,并處理這個結果;還有一個是執(zhí)行對服務器請求的幫助函數(shù),在服務器代碼中通過GetCallbackEventReference()方法獲取這個方法的引用時由asp.net自動生成這個函數(shù)。 下面我以一個詳細的例子來講述如何使用回調,用Dreamweaver創(chuàng)建一個Register. aspx頁面,代碼如下:
<%@ Page Language='C#' ContentType='text/html' ResponseEncoding='gb2312' %> <%@ Implements Interface='System.Web.UI.ICallbackEventHandler' %> <%@ Import Namespace='System.Text' %> <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www./1999/xhtml'> <head> <meta http-equiv='Content-Type' content='text/html; charset=gb2312' /> <title>用戶注冊</title> <script language='javascript'> //客戶端執(zhí)行的方法 //下面的方法是接收并處理服務器方法執(zhí)行的返回結果 function Success(args, context) { message.innerText = args; } //下面的方式是當接收服務器方法處理的結果發(fā)生異常時調用的方法 function Error(args, context) { message.innerText = '發(fā)生了異常'; } </script> <script language='c#' runat='server'> string result=''; // 定義在服務器端運行的回調方法. public void RaiseCallbackEvent(String eventArgument) { if(eventArgument.ToLower().IndexOf('admin')!=-1) { result=eventArgument+'不能作為用戶名注冊。'; } else { result=eventArgument+'可以注冊。'; } //throw new Exception(); } //定義返回回調方法執(zhí)行結果的方法 public string GetCallbackResult() { return result; } //服務器上執(zhí)行的方法 public void Page_Load(Object sender,EventArgs e) { // 獲取當前頁的ClientScriptManager的引用 ClientScriptManager csm = Page.ClientScript; // 獲取回調引用。會在客戶端生成WebForm_DoCallback方法,調用它來達到異步調用。這個方式是微軟寫的方法,會被發(fā)送到客戶端 //注意這里的'Success'和'Error'兩個字符串分別客戶端代碼中定義的兩個javascript函數(shù) //下面的方法最后一個參數(shù)的意義:true表示執(zhí)行異步回調,false表示執(zhí)行同步回調 String reference = csm.GetCallbackEventReference(this, 'args','Success','','Error',false); String callbackScript = 'function CallServerMethod(args, context) {/n' + reference + ';/n }'; // 向當前頁面注冊javascript腳本代碼 csm.RegisterClientScriptBlock(this.GetType(), 'CallServerMethod', callbackScript, true); } </script> </head> <body> <form id='form1' runat='server'> <table border='1' cellpadding='0' cellspacing='0' width='400px'> <tr> <td width='100px'>用戶名</td><td><input type='text' size='10' maxlength='20' id='txtUserName' onblur='CallServerMethod(txtUserName.value,null)' /><span id='message'></span></td> </tr> <tr> <td>密碼</td><td><input type='password' size='10' maxlength='20' id='txtPwd' /></td> </tr> </table> </form> </body> </html>
上面的頁面中我已經添加了足夠詳盡的注視,不過我還是要說明幾點: (1) <%@ Implements Interface='System.Web.UI.ICallbackEventHandler' %>
這句表示當前頁面實現(xiàn)了ICallbackEventHandler接口,如果采用頁面與代碼分離的模式,后臺cs代碼則應是: public partial class Register : System.Web.UI.Page, ICallbackEventHandler { //cs代碼 }
(2) <input type='text' size='10' maxlength='20' id='txtUserName' onblur='CallServerMethod(txtUserName.value,null)' />
這里有一個onblur='CallServerMethod(txtUserName.value,null),表示當用戶名文本框失去焦點之后激發(fā)CallServerMethod這個客戶端方法,這個客戶端方法是由asp.net動態(tài)生成的。 (3) csm.GetCallbackEventReference(this, 'args','Success','','Error',false);
中的'Success'和'Error'分別代表客戶端的javascript函數(shù),可以在代碼中見到,其中'Success'代表調用服務器端方法成功后要執(zhí)行的客戶端方法名,'Error'代表調用服務器端方法失敗時調用的客戶端方法名。 該頁面在客戶端生成的HTML代碼如下: <!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd'> <html xmlns='http://www./1999/xhtml'> <head> <meta http-equiv='Content-Type' content='text/html; charset=gb2312' /> <title>用戶注冊</title> <script language='javascript'> //客戶端執(zhí)行的方法 //下面的方法是接收并處理服務器方法執(zhí)行的返回結果 function Success(args, context) { message.innerText = args; } //下面的方式是當接收服務器方法處理的結果發(fā)生異常時調用的方法 function Error(args, context) { message.innerText = '發(fā)生了異常'; } </script> </head> <body> <form name='form1' method='post' action='register.aspx' id='form1'> <div> <input type='hidden' name='__EVENTTARGET' id='__EVENTTARGET' value='' /> <input type='hidden' name='__EVENTARGUMENT' id='__EVENTARGUMENT' value='' /> <input type='hidden' name='__VIEWSTATE' id='__VIEWSTATE' value='/wEPDwUKMjA0MjMxNTU1OGRkIv6UMIqGy3vfPLfPRjEbuTwUrf8=' /> </div> <script type='text/javascript'> <!-- var theForm = document.forms['form1']; if (!theForm) { theForm = document.form1; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } // --> </script> <script src='/WebResource.axd?d=CcZ-_AaHZnD65xnNHEUijg2&t=633578466781093750' type='text/javascript'></script> <script type='text/javascript'> <!-- function CallServerMethod(args, context) { WebForm_DoCallback('__Page',args,Success,'',Error,false); }// --> </script> <table border='1' cellpadding='0' cellspacing='0' width='400px'> <tr> <td width='100px'>用戶名</td><td><input type='text' size='10' maxlength='20' id='txtUserName' onblur='CallServerMethod(txtUserName.value,null)' /><span id='message'></span></td> </tr> <tr> <td>密碼</td><td><input type='password' size='10' maxlength='20' id='txtPwd' /></td> </tr> </table> <script type='text/javascript'> <!-- WebForm_InitCallback();// --> </script> </form> </body> </html>
在生成的HTML代碼中多了幾段javascipt教本塊,下面分別說明: (1)第一部分 <script type='text/javascript'> <!-- var theForm = document.forms['form1']; if (!theForm) { theForm = document.form1; } function __doPostBack(eventTarget, eventArgument) { if (!theForm.onsubmit || (theForm.onsubmit() != false)) { theForm.__EVENTTARGET.value = eventTarget; theForm.__EVENTARGUMENT.value = eventArgument; theForm.submit(); } } // --> </script>
這部分代碼是每個asp.net頁面發(fā)送到客戶端都會生成的,用于提交當前表單,其中eventTarget參數(shù)表示激發(fā)提交事件的控件,eventArgument參數(shù)表示發(fā)生該事件時的參數(shù)信息。 (2)第二部分 <script src='/WebResource.axd?d=CcZ-_AaHZnD65xnNHEUijg2&t=633578466781093750' type='text/javascript'></script>
這部分代碼是用來生成一些用于Ajax調用的js腳本。說穿了,asp.net之所以開發(fā)起來方便,是因為微軟在幕后默默地為我們做了很多工作,回調的本質其實就是Ajax調用。 我們可以將“/WebResource.axd?d=CcZ-_AaHZnD65xnNHEUijg2&t=633578466781093750”這部分拷貝到瀏覽器地址欄中,如下圖:
回車之后會彈出一個下載文件對話框,如下圖:  將這個頁面保存到本地,雖然默認的保存文件的后綴為“.axd”,但它其實是一個文本文件,里面是一些javascript代碼,我們可以用記事本打開,在里面我們可以看到“WebForm_DoCallback”這個方法,如下:
在這個axd文件里做了很多幕后工作,所以我們的回調才相對比較簡單。 (3)第三部分 <script type='text/javascript'> <!-- function CallServerMethod(args, context) { WebForm_DoCallback('__Page',args,Success,'',Error,false); }// --> </script>
這部分代碼是后臺生成的,通過獲取Page類的ClientScript屬性,也就是ClientScriptManager的實例注冊到頁面的,里面定義了兩個javascript函數(shù):CallServerMethod函數(shù)和WebForm_DoCallback函數(shù),并且是在CallServerMethod函數(shù)中調用WebForm_DoCallback函數(shù)。 (4)第四部分 <script type='text/javascript'> <!-- WebForm_InitCallback();// --> </script>
這部分代碼也是幕后生成的,這個javascript函數(shù)也可以在那個axd文件中找到。如下圖: 當我們在以除“admin”之外的字符串作為用戶名并移開焦點之后,會得到可以注冊的提示,如下圖:  當我們輸入“admin”作為用戶名時的結果:
另外,我們將服務器端執(zhí)行的方法做如下處理,也就是RaiseCallbackEvent(String eventArgument)這個方法,我們在這里拋出一個異常,代碼如下:
// 定義在服務器端運行的回調方法. public void RaiseCallbackEvent(String eventArgument) { /* if(eventArgument.ToLower().IndexOf('admin')!=-1) { result=eventArgument+'不能作為用戶名注冊。'; } else { result=eventArgument+'可以注冊。'; } */ throw new Exception(); }
再次運行,無論我們以什么作為用戶名,都會得到如下結果:  之所以會出現(xiàn)“發(fā)生了異常”這個字符串,是因為我們定義了function Error(args, context)這個javascript函數(shù),并且把它作為調用服務器端方法發(fā)生異常時的客戶端處理函數(shù),它的處理方式就是顯示“發(fā)生了異常”這個字符串。 后記:看到很多朋友在http://blog.csdn.net/zhoufoxcn上留言,急切想看到后續(xù)文章,所以不顧白天工作繁忙繼續(xù)寫了這一篇文章,在這篇文章里花了較大篇幅介紹回調技術,怕初學者理解起來有困難,所以我在代碼中用了大量注釋,并額外解釋了一些關鍵代碼,希望大家能夠理解。下一篇將介紹asp.net基本服務器控件。 寫完這篇文章已經是2008-9-26 日凌晨了,我發(fā)現(xiàn)這個系列的文章該叫《asp.net晨話》而不是叫《asp.net夜話》了,因為每次我寫完的時候都已經是凌晨了,呵呵。不過有這么多朋友熱情的留言鼓勵,我的動力還是很大的。下一篇我已經基本完成了,不過需要我抽空再做一下文字校正工作。工作比較忙,時間特別不充足,希望大家多多諒解:) 另外,如果大家有什么好的建議請到我的博客http://blog.csdn.net/zhoufoxcn留言,我會經常留意大家的留言的。 周金橋 2008-9-28 0點50分
|