關於ASP.NET的知識太多了,這裡只列出一些問題留作自己下次思考並隨時會補充一些知識點。
頁生命週期
關於asp.net的頁面生命週期先看兩幅我收藏的圖片,第一幅圖片有點瑕疵,第二幅圖片資訊量少了點,但結合兩張圖片能說明很多東西。
再提幾個自己比較關心的問題:
頁面聲生命週期在整個http請求中的位置?
控制項的事件執行順序是什嗎?
控制項什麼時候進行資料繫結的?
ViewState的TrackViewState是什麼時候開始的?
什麼時候載入ViewState儲存的值的?
控制項是如何追趕生命週期的?只針對動態建立的控制項?
伺服器控制項
關於伺服器控制項同樣也提幾個問題留給自己思考(答案)
什麼是控制項樹?
html控制項,html伺服器控制項以及asp.net的web伺服器控制項的區別?
ASP.NET是如何處理runat="server"?
伺服器控制項的OnClick是怎麼處理的?
什麼是嵌套控制項的轉寄事件?
ViewState和回傳值(PostData)
照例我們來看一個例子:一個頁面我們在頁面載入的時候綁定DropDownList,另外還有一個Button沒有綁定任何處理事件只是希望點擊它的時候能夠回傳頁面:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="test3.aspx.cs" Inherits="test.test3" %><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head runat="server"> <title></title></head><body> <form id="form1" runat="server"> <div> <asp:DropDownList ID="DropDownList1" runat="server" EnableViewState="True"> </asp:DropDownList> <asp:Button ID="Button1" runat="server" Text="PostBack" /> </div> </form></body></html>
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { this.DropDownList1.DataSource = QueryDatabase(); this.DropDownList1.DataBind(); } } private List<string> QueryDatabase() { List<string> result = new List<string>(); result.Add("北京"); result.Add("上海"); result.Add("深圳"); result.Add("安徽"); result.Add("廣東"); return result; }
運行這個頁面我們發現一切運行很正常,每次回傳後DropDownList的資料仍然存在並且仍然顯示的是上次選擇的值。看我們的代碼if(!IsPostBack)這行,這表示我們只是在第一次頁面載入的時候綁定了DropDownList,之所以能夠一切正常,通過前面對ViewState的介紹不難理解是因為頁面Load的時候TrackViewState()已經啟動了,這時候DropDownList裡的資料已經通過序列化和還原序列化以及LoadViewState()和SaveViwState()方法在回傳之間進行了儲存。但是如果這個下拉式清單繫結資料很大,那麼每次頁面回傳來回都要攜帶大量的_ViewState資訊,這在低頻寬下是很影響速度的。於是我麼考慮通過禁用DropDownList的EnableViewState屬性而是每次都重新綁定資料(衡量這兩者方式之間的效能)來避免開銷,當然這時候像上面那樣只在第一次載入頁面時候綁定資料是不可行的了,去掉if(!IsPostBack)!貌似可以,但是會發現一個問題就是每次回傳後下拉式清單顯示不再是上次選擇的值了。為什麼呢?很多人包括之前的我都會認為是因為我們禁用了DropDownList的ViewState的原因,那讓我們在OnLoad中每次重新綁定資料並將EnableViewState屬性設為True來試試(真實環境要這樣做的話那就是造成效能的雙重損失了)。結果發現上面的問題依然存在,每次回傳後下發列表顯示的將是”北京“,不管你之前選擇的是什麼。這又是為什嗎?原來在頁面回傳的時候還有一些東西並不是通過ViewState來儲存的,而是通過另外一個索引值對的玩意,它叫做回傳值(PostData),預設是以Post請求回傳的,我們可以修改為Get方式,在form表單中添加method="get":
<form id="form1" runat="server" method="get">
我們可以發現瀏覽器的url發生了變化,之所以向=像下面這樣一大堆是因為進行了加密。
http://localhost:5738/test3.aspx?__VIEWSTATE=%2FwEPDwUJMzkwNTA5NDc2D2QWAgIDD2QWAgIBDxAPFgIeC18hRGF0YUJvdW5kZ2QQFQUG5YyX5LqsBuS4iua1twbmt7HlnLMG5a6J5b69BuW5v%2BS4nBUFBuWMl%2BS6rAbkuIrmtbcG5rex5ZyzBuWuieW%2BvQblub%2FkuJwUKwMFZ2dnZ2dkZGSNKet80L43lwcKiYK8hHkQraKPiqkyOu7uuQGF0c73yQ%3D%3D&__EVENTVALIDATION=%2FwEWBwLM2oqDDQK0tbT8CwK7n8DiBQLS0NCwBQK6hPj0CgLMv%2FT7CwKM54rGBrqd0YO1IQK9DNW9Us7zGuPCFrnql5ep1WBFw2V1eKTq&DropDownList1=%E6%B7%B1%E5%9C%B3&Button1=PostBack
那麼為什麼我們在OnLoad中綁定DropDownList回傳的時候無法擷取回傳的值呢?我們回頭看下那張頁面生命週期的圖,處理回傳資料(PostData)的方法是ProcessPostData()調用了兩次,一次是在頁面Load事件之前,一次是在Load之後。對於靜態控制項來說(我們代碼中那樣)將回傳資料載入到控制項只會發生在第一次ProcessPostData中,所以之所以下拉式清單會忘記上次的選擇是因為Load發生在ProcessPostData()之後,載入回傳資料的時 候DropDownList還沒有ListItem。而對於在OnLoad中動態建立的控制項來說還有一次ProcssPostData的機會,所以解決上面問題的方法就是將資料繫結提前到ProcessPostData()之前比如OnInit中:
protected void Page_Load(object sender, EventArgs e) { //if (!IsPostBack) //{ // this.DropDownList1.DataSource = QueryDatabase(); // this.DropDownList1.DataBind(); //} } private List<string> QueryDatabase() { List<string> result = new List<string>(); result.Add("北京"); result.Add("上海"); result.Add("深圳"); result.Add("安徽"); result.Add("廣東"); return result; } protected override void OnInit(EventArgs e) { this.DropDownList1.DataSource = QueryDatabase(); this.DropDownList1.DataBind(); base.OnInit(e); }
至於到底是每次重新繫結控制項還是利用ViewState的效能更高或者更滿足情境要看具體情況了。
控制項的”追趕“
上面的例子雖然解決了我們遇到的問題:濫用ViewState帶來的效能損失以及DropDownList忘記了上次的選擇。但是通過提前綁定資料和禁用控制項的ViewState帶來了另一個問題,因為禁用了DropDownList的EnableViewState屬性將導致我們的SelectedIndexChanged無法觸發。注意將DropDownList的AutoPostBack屬性設為True,否則不會立即回傳要等到下次提交以及要意識到有PostData的存在,不要幹擾你的判斷,之所以強調指點是我發現一個很”奇怪“的現象,看下面的例子:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="test3.aspx.cs" Inherits="test.test3" %><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head runat="server"> <title></title></head><body> <form id="form1" runat="server" method="get"> <div> <asp:DropDownList ID="DropDownList1" runat="server" EnableViewState="False" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged" AutoPostBack="True"> </asp:DropDownList> <asp:Button ID="Button1" runat="server" Text="PostBack" /> </div> </form></body></html>
protected void Page_Load(object sender, EventArgs e) { //if (!IsPostBack) //{ // this.DropDownList1.DataSource = QueryDatabase(); // this.DropDownList1.DataBind(); //} } private List<string> QueryDatabase() { List<string> result = new List<string>(); result.Add("安徽"); result.Add("北京"); result.Add("上海"); result.Add("深圳"); result.Add("廣東"); return result; } protected override void OnInit(EventArgs e) { this.DropDownList1.DataSource = QueryDatabase(); this.DropDownList1.DataBind(); base.OnInit(e); } protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e) { //說明SelectedItem不是通過ViewState回傳的 //if (DropDownList1.SelectedItem.Text == "上海") //{ // Response.Write("你選擇了上海"); //} Response.Write(DropDownList1.Text); }
對前面例子中的DropDwonList增加了一個SelectedIndexChanged事件,在回應時間的函數裡將當前下拉框的文本輸出到頁面,和上面一樣DropDownList的ViewState是禁用,按照我的預想現在應該是選擇下拉框時候頁面沒有反應,但是實踐的結果卻是除了挑選清單第一個元素選擇其他的頁面仍然能輸出,當將DropDownList的EnableViewState屬性設為True結果和預想的一樣。難道下拉式清單除了第一個元素是通過ViewState回傳其餘的都是通過PostData回傳??總感覺怪怪的肯定另有原因。這個暫且不管既然禁用ViewState的時候選擇第一個元素和我們想要的結果一直,我們姑且作為參照。言歸正傳:既然通過上一個例子的方法沒法解決禁用ViewState帶來的影響那麼有沒有一個兩全其美的方法呢,所謂兩全其美就是既能在初始化下拉式清單資料的時候避免使得ViewState不必要的增大,又能在需要它的時候使其保持狀態呢?有!這個方法就是動態建立控制項,在說明為什麼之前先看下如何做,先刪除代碼中靜態添加的DropDownList,然後再OnInit中動態建立它:
DropDownList refDl = null; protected void Page_Load(object sender, EventArgs e) { } private List<string> QueryDatabase() { List<string> result = new List<string>(); result.Add("安徽"); result.Add("北京"); result.Add("上海"); result.Add("深圳"); result.Add("廣東"); return result; } protected override void OnInit(EventArgs e) { DropDownList DropDownList1 = new DropDownList(); DropDownList1.DataSource = QueryDatabase(); DropDownList1.DataBind(); //儲存引用 refDl = DropDownList1; //AutoPostBack設定為true DropDownList1.AutoPostBack = true; //動態建立的控制項必須顯示的綁定事件 DropDownList1.SelectedIndexChanged += new EventHandler(DropDownList1_SelectedIndexChanged); this.Form.Controls.Add(DropDownList1); base.OnInit(e); } protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e) { //說明SelectedItem不是通過ViewState回傳的 //if (DropDownList1.SelectedItem.Text == "上海") //{ // Response.Write("你選擇了上海"); //} Response.Write(refDl.Text); } }
現在說明為什麼,這個涉及到兩個問題?
- 動態建立的控制項何時開始跟蹤它的ViewState?
- 當動態建立的控制項的狀態落後與頁面生命週期的時候是如何處理的?
我們可以再頁面生命週期的任何階段去動態建立控制項,同時我們從上面第一張圖中知道TrackViewState()是在OnInit階段完成的,而控制項的OnInit是在頁面的OnInit前遞迴完成的,那麼在頁面的OnInit中甚至極端情況在頁面的OnPreRender階段中動態建立一個控制項,那麼這個控制項是不是會錯過自身的TrackViewState()呢?關鍵在於上面的this.Form.Controls.Add(DropDownList1);中的Add()方法,關於這個方法以及內部的細節我也在研究,但總的來說這個方法會檢查控制項所處的階段,如果落後頁面的生命週期的話會有一個”追趕“的過程,會執行這個控制項經理的一些方法當然也包括TrackViewState()但好像也會忽略一些私人方法,因為這個追趕過程是發生在Add()之後所以這之前繫結資料自然不會被當成”髒資料“,也不會添加到ViewState中,同時因為追趕所以SelectedIndexChange等事件依然會正常觸發。