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