熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

ASP.NET Web Page應用深入探討

2013-11-13 10:14:16  來源: .NET編程 

  服務器腳本基礎介紹

  首先我們先復習一下Web服務器頁面的基本執行方式

  客戶端通過在浏覽器的地址欄敲入地址來發送請求到服務器端

  服務器接收到請求之後發給相應的服務器端頁面(也就是腳本)來執行腳本產生客戶端的響應發送回客戶端

  客戶端浏覽器接收到服務器傳回的響應對Html進行解析將圖形化的網頁呈現在用戶面前

  對於服務器和客戶端的交互通常通過下面幾種主要方式

  Form這是最主要的方式標准化的控件來獲取用戶的輸入Form的提交將數據發送給服務器端處理

  QueryString通過在Url後面帶參數達到將參數傳送給服務器這種方式其實跟Get方式的Form是一樣的

  Cookies這是一種比較特殊的方式通常用於用戶身份的確認

  ASPNet簡介

  傳統的服務器腳本語言如ASPJSP等編寫服務器腳本的方式大同小異都是在Html中嵌入解釋或編譯執行的代碼由服務器平台執行這些代碼來生成Html對於這類似的腳本頁面的生存周期實際上很簡單就是從開頭至末尾執行完所有的代碼當然用Java編寫的Servlet可以編寫更復雜的代碼但是從結構上看和JSP沒什麼區別

  ASPNet的出現打破了這種傳統ASPNet采用了CodeBehind技術和服務器端控件加入了服務器端的事件的概念改變了腳本語言編寫的模式更加貼近Window編程使Web編程更加簡單直觀但是我們要看到ASPNet本身並沒有改變Web編程的基本模式只是封裝了一些細節提供了一些易用的功能使代碼更容易編寫和維護從某種程度上來說將服務器端執行的方式復雜化了這就是我們今天要討論的主體ASPNet Web Page的生存周期

  ASPNet請求處理模式

  我們說ASPNet的Web Page並沒有脫離Web編程的模式所以它仍然是以 請求>接收請求>處理請求>發送響應 這樣的模式在工作每一次與客戶端的交互都會引發一次新的請求所以一個Web Page的生命周期是以一次請求為基礎的

  當IIS收到客戶端的請求的時候會將請求交給aspnet_wp這個進程來處理這個進程會查看請求的應用程序域是否存在如果不存在則會創建一個然後會創建一個Http運行時(HttpRuntime)來處理請求這個運行時為當前應用程序提供一組 ASPNET 運行時服務(摘自MSDN)

  HttpRuntime在處理請求的時候會維護一系列的應用程序實例也就是應用程序的Global類(globalasax)的實例這些實例在沒有請求的時候會存放在一個應用程序池中(實際上應用程序池由另一個類來維護HttpRuntime只是簡單的調用)每接收到一個請求HttpRuntime都會獲取一個閒置的實例來處理請求這個實例在請求結束前不會處理其他的請求處理完畢之後它又會回到池中一個實例在其生存期內被用於處理多個請求但它一次只能處理一個請求(摘自MSDN)

  當應用程序實例處理請求的時候它會創建請求頁面類的實例執行它的ProcessRequest方法來處理請求這個方法也就是Web Page生命周期的開始

  Aspx頁面與CodeBehind

  在深入了解頁面的生命周期之前我們先來探討一些Aspx與CodeBehind之間的關系

  <%@ Page language=c# Codebehind=WebFormaspxcs Inherits=MyNamespaceWebForm %>

  相信使用過CodeBehind技術的朋友對ASPX頂部的這句話應該是非常熟悉了我們來一項一項的分析它

  Page language=c# 這個就不用多說了吧

  Codebehind=WebFormaspxcs 這一句表示綁定的代碼文件

  Inherits=MyNamespaceWebForm 這句非常重要它表示頁面繼承的類名稱也就是CodeBehind的代碼文件中的類這個類必須從SystemWebWebControlsPage派生

  從上面我們可以分析出實際上CodeBehind中的類就是頁面(ASPX)的基類到這裡可能有些朋友要問了在編寫ASPX的時候完全是按照ASP的方式在Html中嵌入代碼或者嵌入服務器控件沒有看到所謂的影子啊?

  這個問題實際上並不復雜各位使用ASPNet編程的朋友可以到你們的系統盤\WINDOWS\MicrosoftNET\Framework\<版本號>\Temporary ASPNET Files這個目錄下這個下面就放了所有本機上存在的ASPNet應用程序的臨時文件子目錄的名稱就是應用程序的名稱然後再下去兩層(為了保證唯一ASPNet自動產生了兩層子目錄並且子目錄名稱是隨機的)然後我們會發現有很多類似yfygjhcdllxeunjudll這樣的鏈接庫以及komeebpcsfalckavcs這樣的源文件實際上這就是ASPX被ASPNet動態編譯後的結果打開這些源文件我們可以發現

  public class WebForm_aspx MyNamespaceWebForm SystemWebSessionStateIRequiresSessionState

  這就印證了我們前面的說法ASPX是代碼綁定類的子類它的名稱是ASPX文件名加上_aspx後綴通過研究這些代碼我們可以發現實際上所有aspx中定義的服務器控件都是在這些代碼中生成的然後動態產生這些代碼的時候把原來在ASPX中嵌入的代碼寫在了相應的位置

  當某個頁面第一次被訪問的時候Http運行時就會使用一個代碼生成器去解析ASPX文件並生成源代碼並編譯然後以後的訪問就直接調用編譯後的dll這也是為什麼ASPX第一次訪問的時候非常慢的原因

  解釋了這個問題我們再來看另一個問題我們在使用代碼綁定的時候在設計頁面拖一個控件然後切換到代碼視圖就可以直接在Page_Load中使用這個控件了既然控件是在子類中產生的那為什麼在父類中可以直接使用呢?

  實際上我們可以發現每當用VSNet拖一個控件到頁面上代碼綁定文件中總是會類似這樣的添加一個聲明

  protected SystemWebWebControlsButton Button

  我們可以發現這個字段被聲明成protected而且名字與ASPX中控件的ID一致仔細想一想這個問題就迎刃而解了我們前面提到ASPX的源代碼是被生成器動態生成和編譯的生成器會產生動態生成每一個服務器控件的代碼在生成的時候它會檢查父類有沒有聲明這個控件如果聲明了它會添加類似下面的一句代碼

  thisDataGrid = __ctrl

  這個__ctrl就是生成該控件的變量這時候它就把控件的引用賦給了父類中相應的變量這也是為什麼父類中的聲明必須為protected(實際上也可以為public)因為要保證子類能夠調用

  然後在執行Page_Load的時候因為這時候父類的聲明已經被子類中的初始化代碼賦了值所以我們就可以使用這個字段來訪問對應的控件了解了這些我們就不會犯在代碼綁定文件中的構造器裡使用控件造成空引用的異常的錯誤了因為構造器是最先執行的這時候子類的初始化還沒有開始所以父類中的字段是空值至於子類是什麼時候初始化我們放到後面討論

  頁面生存周期

  現在回到第三個標題中講到的內容我們講到了HttpApplication的實例接收請求並創建頁面類的實例實際上這個實例也就是動態編譯的ASPX的類的一個實例上一個標題中我們了解到ASPX實際上是代碼綁定中類的子類所以它繼承了所有的protected方法

  現在我們來看看VSNet自動生成的CodeBehind類的代碼以此來開始我們對頁面生命周期的探討

  #region Web Form Designer generated code

  override protected void OnInit(EventArgs e)

  { // // CODEGEN該調用是 ASPNET Web 窗體設計器所必需的

  // InitializeComponent()baseOnInit(e)}

  /// <summary> /// 設計器支持所需的方法 不要使用代碼編輯器修改/// 此方法的內容

  /// </summary>

  private void InitializeComponent()

  { thisDataGridItemDataBound += new SystemWebUIWebControlsDataGridItemEventHandler(thisDataGrid_ItemDataBound)

  thisLoad += new SystemEventHandler(thisPage_Load)}

  #endregion

  這個就是使用VSNet產生的Page的代碼我們來看這裡面有兩個方法一個是OnInit一個是InitializeComponent後者被前者調用實際上這就是頁面初始化的開始在InitializeComponent中我們看到了控件的事件聲明和Page的Load聲明

  下面是從MSDN中摘錄的一段描述和一個頁面生命周期方法和事件觸發的順序表

  每次請求 ASPNET 頁時服務器就會加載一個 ASPNET 頁並在請求完成時卸載該頁頁及其包含的服務器控件負責執行請求並將 HTML 呈現給客戶端雖然客戶端和服務器之間的通訊是無狀態的和斷續的但是必須使客戶感覺到這是一個連續執行的過程

  這種連續性假象是由 ASPNET 頁框架頁及其控件實現的回發後控件的行為必須看起來是從上次 Web 請求結束的地方開始的雖然 ASPNET 頁框架可使執行狀態管理相對容易一些但是為了獲得連續性效果控件開發人員必須知道控件的執行順序控件開發人員需要了解在控件生命周期的各個階段控件可使用哪些信息保持哪些數據控件呈現時處於哪種狀態例如在填充頁上的控件樹之前控件不能調用其父級 下表提供了控件生命周期中各階段的高級概述有關詳細信息請點擊表中的鏈接

  階段 控件需要執行的操作 要重寫的方法或事件初始化 初始化在傳入 Web 請求生命周期內所需的設置請參閱處理繼承的事件 Init 事件(OnInit 方法)

  加載視圖狀態 在此階段結束時就會自動填充控件的 ViewState 屬性詳見維護控件中的狀態中的介紹控件可以重寫 LoadViewState 方法的默認實現以自定義狀態還原 LoadViewState 方法處理回發數據 處理傳入窗體數據並相應地更新屬性請參閱處理回發數據

  注意 只有處理回發數據的控件參與此階段 LoadPostData 方法 (如果已實現IPostBackDataHandler)

  加載 執行所有請求共有的操作如設置數據庫查詢此時樹中的服務器控件已創建並初始化狀態已還原並且窗體控件反映了客戶端的數據請參閱處理繼承的事件 Load 事件(OnLoad 方法)

  發送回發更改通知 引發更改事件以響應當前和以前回發之間的狀態更改請參閱處理回發數據

  注意 只有引發回發更改事件的控件參與此階段 RaisePostDataChangedEvent 方法(如果已實現 IPostBackDataHandler)

  處理回發事件 處理引起回發的客戶端事件並在服務器上引發相應的事件請參閱捕獲回發事件

  注意 只有處理回發事件的控件參與此階段 RaisePostBackEvent 方法(如果已實現 IPostBackEventHandler)

  預呈現 在呈現輸出之前執行任何更新可以保存在預呈現階段對控件狀態所做的更改而在呈現階段所對的更改則會丟失請參閱處理繼承的事件 PreRender 事件(OnPreRender 方法)

  保存狀態 在此階段後自動將控件的 ViewState 屬性保持到字符串對象中此字符串對象被發送到客戶端並作為隱藏變量發送回來為了提高效率控件可以重寫 SaveViewState 方法以修改 ViewState 屬性請參閱維護控件中的狀態 SaveViewState 方法呈現 生成呈現給客戶端的輸出請參閱呈現 ASPNET 服務器控件 Render 方法處置 執行銷毀控件前的所有最終清理操作在此階段必須釋放對昂貴資源的引用如數據庫鏈接請參閱 ASPNET 服務器控件中的方法

  Dispose 方法卸載 執行銷毀控件前的所有最終清理操作控件作者通常在 Dispose 中執行清除而不處理此事件 UnLoad 事件(On UnLoad 方法)

  從這個表裡面我們可以清楚的看到一個Page從裝載到卸載之間調用的方法和觸發的時間接下來我們就深入的對其進行一些分析

  看了上面的表細心的朋友可能要問了既然OnInit是頁面生命周期的開始而我們在上一講中談到控件在子類中被創建那麼在這裡實際上在InitializeComponent方法中我們已經可以使用父類中聲名的字段了那麼就意味著子類的初始化更在這之前?

  在第三個標題中我們講到了頁面類的ProcessRequest才是真正意義上的頁面聲明周期的開始這個方法是由HttpApplication調用的(其中調用的方式比較復雜有機會單獨撰文來講解)一個Page對請求的處理就是從這個方法開始通過反編譯Net類庫來查看源代碼我們發現在SystemWebWebControlsPage的基類SystemWebWebControlsTemplateControl(它是頁面和用戶控件的基類)中定義了一個FrameworkInitialize虛擬方法然後在Page的ProcessRequest中最先調用了這個方法在生成器生成的ASPX的源代碼中我們發現了這個方法的蹤影所有的控件都在這個方法中被初始化頁面的控件樹就在這個時候產生

  接下來的事情就簡單了我們來逐步分析頁面生命周期的每一項

  初始化

  初始化對應Page的Init事件和OnInit方法

  如果要重寫MSDN推薦的方式是重載OnInti方法而不是增加一個Init事件的代理這兩者是有差別的前者可以控制調用父類OnInit方法的順序而後者只能在父類的OnInit後執行(實際上是在OnInit裡面被調用的)

   加載視圖狀態

  這是個比較重要的方法我們知道對於每次請求實際上是由不同的頁面類實例來處理的為了保證兩次請求間的狀態ASPNet使用了ViewState

  LoadViewState方法就是從ViewState中獲取上一次的狀態並依照頁面的控件樹的結構用遞歸來遍歷整個樹將對應的狀態恢復到每一個控件上

   處理回發數據

  這個方法是用來檢查客戶端發回的控件數據的狀態是否發生了改變方法的原型

  public virtual bool LoadPostData(string postDataKey NameValueCollection postCollection)

  postDataKey是標識控件的關鍵字(也就是postCollection中的Key)postCollection是包含回發數據的集合我們可以重寫這個方法然後檢查回發的數據是否發生了變化如果是則返回一個True如果控件狀態因回發而更改則 LoadPostData 返回 true否則返回 false頁框架跟蹤所有返回 true 的控件並在這些控件上調用 RaisePostDataChangedEvent(摘自MSDN)

  這個方法是SystemWebWebControlsControl中定義的也是所有需要處理事件的自定義控件需要處理的方法對於我們今天討論的Page來說可以不用管它

   加載

  加載對應Load事件和OnLoad方法對於這個事件相信大多數朋友都會比較熟悉用VSNet生成的頁面中的Page_Load方法就是響應Load事件的方法對於每一次請求Load事件都會觸發Page_Load方法也就會執行相信這也是大多數人了解ASPNet的第一步

  Page_Load方法響應了Load事件這個事件是在SystemWebWebControlControl類中定義的(這個類是Page和所有服務器控件的祖宗)並且在OnLoad方法中被觸發

  很多人可能碰到過這樣的事情寫了一個PageBase類然後在Page_Load中來驗證用戶信息結果發現不管驗證是否成功子類頁面的Page_Load總是會先執行這個時候很可能留下一些安全性的隱患用戶可能在沒有得到驗證的情況下就執行了子類中的Page_Load方法

  出現這個問題的原因很簡單因為Page_Load方法是在OnInit中被添加到Load事件中的而子類的OnInit方法中是先添加了Load事件然後再調用baseOnInit這樣就造成了子類的Page_Load被先添加那麼先執行了

  要解決這個問題也很簡單有兩種方法

  ) 在PageBase中重載OnLoad方法然後在OnLoad中驗證用戶然後調用baseOnLoad因為Load事件是在OnLoad中觸發這樣我們就可以保證在觸發Load事件之前驗證用戶

  ) 在子類的OnInit方法中先調用baseOnInit這樣來保證父類先執行Page_Load

   發送回發更改通知

  這個方法對應第步的處理回發數據如果處理回發數據返回True頁面框架就會調用此方法來觸發數據更改的事件所以自定義控件的回發數據更改事件需要在此方法中觸發

  同樣這個方法對於Page來說沒有太大的用處當然你也可以在Page的基礎上自己定義數據更改的事件這當然也是可以的

   處理回發事件

  這個方法是大多數服務器控件事件引發的地方當請求中包含控件事件觸發的信息時(服務器控件的事件是另一個論題我會在不久將來另外撰文討論)頁面控件會調用相應控件的RaisePostBackEvent方法來引發服務器端的事件

  這裡又引出一個常見的問題

  經常有網友問為什麼修改提交後的數據並沒有更改

  多數的情況都是他們沒有理解服務器事件的觸發流程我們可以看出觸發服務器事件是在Page的Load之後也就是說頁面會先執行Page_Load然後才會執行按鈕(這裡以按鈕為例)的點擊事件很多朋友都是在Page_Load中綁定數據然後在按鈕事件中處理更改這樣做有一個毛病Page_Load永遠都是在按鈕事件之前執行那麼意味著數據還沒來得及更改Page_Load中的數據綁定的代碼就先執行了原有的數據又賦給了控件那麼執行按鈕事件的時候實際上獲得的是原有的數據那麼更新當然就沒有效果了

  更改這個問題也非常簡單比較合理的做法是把數據綁定的代碼寫成一個方法我們假設為BindData

  private void BindData()

  { //綁定數據}

  然後修改PageLoad

  private void Page_Load( object senderEventArgs e )

  { if( !IsPostBack )

  { BindData() //在頁面第一次訪問的時候綁定數據}

  最後在按鈕事件中

  private Button_Click( object senderEventArgs e )

  { //更新數據BindData()//重新綁定數據}

  預呈現

  最終請求的處理都會轉變為發回服務器的響應預呈現這個階段就是執行在最終呈現之前所作的狀態的更改因為在呈現一個控件之前我們必須根據它的屬性來產生Html比如Style屬性這是最典型的例子在預呈現之前我們可以更改一個控件的Style當執行預呈現的時候我們就可以把Style保存下來作為呈現階段顯示Html的樣式信息

  保存狀態

  這個階段是針對加載狀態的我們多次提到請求之間是不同的實例在處理所以我們需要把本次的頁面和控件的狀態保存起來這個階段就是把狀態寫入ViewState的階段

  呈現

  到這裡實際上頁面對請求的處理基本就告一段落了在Render方法中會遞歸整個頁面的控件樹依次調用Render方法把對應的Html代碼寫入最終響應的流中

  處置

  實際上就是Dispose方法在這個階段會釋放占用的資源例如數據庫連接

  卸載

  最後頁面會執行OnUnLoad方法觸發UnLoad事件處理在頁面對象被銷毀之前的最後處理實際上ASPNet提供這個事件只是設計上的考慮通常資源的釋放都會在Dispose方法中完成所以這個方法也變成雞肋了

  我們簡單的介紹了頁面的生存周期對於服務器端事件的處理做了不太深入的講解今天主要是想大家了解頁面執行的周期對於服務器控件的事件和生存期我會在後續在寫一些文章來探討

  這些內容是我在學習ASPNet的時候對Page研究的一些心得具體的細節沒有很詳細的探討更多的內容請大家參考MSDN但是我舉了一些初學者常犯的錯誤和出現錯誤的原因希望可以給大家帶來啟發


From:http://tw.wingwit.com/Article/program/net/201311/13003.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.