開篇:毫無疑問,ASP.Net WebForm是微軟推出的一個跨時代的Web開發模式,它將WinForm開發模式的快捷便利的優勢移植到了Web開發上,咱們只要學會三步:拖控件→設屬性→綁事件,即可以行走於天下。但這樣真的就能夠走一生嗎?實際上,ASP.Net常常被噴的詬病就在於WebForm以及只會拖控件的ASP.Net程序員,每每大型互聯網系統也沒有采用WebForm的模式進行開發。可是,WebForm並非一無可取,而是咱們沒有用好,還有不少東西咱們知其然不知其因此然,如今咱們就來對這些平時所不注意但又十分關鍵的東西一探究竟。javascript
在WebForm中,全部的頁面請求都是以aspx文件做爲請求對象(靜態化和僞靜態的除外)。例如上圖中,訪問者在瀏覽器端經過輸入URL:blog/index.aspx向服務器端發送請求,服務器端首先找到這個index.aspx,而後建立頁面對象(index.aspx.cs文件中的類對象),調用這個頁面對象中的ProcessRequest方法和Page_Load方法(在此過程當中,有可能須要訪問數據庫)來生成aspx頁面的全部html內容,最後將生成好的html返回給瀏覽器端。html
所以,咱們能夠知道,服務器端對aspx的處理過程其實就是一個渲染生成html的過程。java
經過實踐可知,在aspx中除了<%%>的內容和runat="server"的內容,其餘都是原樣輸出。這是由於咱們在aspx中能夠藉助<%%>寫入C#代碼,就跟ASP、PHP同樣的風格。可是,在實際開發中並不建議這麼來作,由於它違反了CodeBehind的原則,不利於職責的分離。git
①直接寫入C#業務邏輯代碼程序員
1 <% 2 for (int i = 0; i < 5; i++) 3 { 4 Response.Write("I am a webform page.<br/>"); 5 } 6 %>
②獲取C#方法的返回值github
假設頁面後端代碼中有一個GetServerTime的方法,它只有一句代碼:return DateTime.Now.ToString();。頁面中只須要經過<%= 方法名() %>便可獲取該方法的返回值。web
<%= GetServerTime() %>
③aspx中可以訪問的方法的訪問修飾符只能爲public和protected:這是由於aspx和aspx.cs之間的關係是編譯生成後aspx和aspx.cs會建立兩個類,而且aspx繼承自aspx.cs中的類,在面向對象中子類要訪問父類的方法,那麼方法的訪問修飾符必須爲public或protected。(後面會講到aspx和aspx.cs的關係,不要急)數據庫
ashx是通常處理程序,它是一個實現了IHttpHandler的輕量級處理程序,處理操做都在ProcessRequest方法中完成。後端
而aspx則至關於一個特殊的、高級的ashx,aspx所對應的父類是System.Web.UI.Page這個類,經過查看Page類的定義,咱們能夠看到Page也實現了IHttpHandler的這個接口。另外之因此說它是高級的ashx,是由於aspx幫咱們封裝了許多底層的操做,使得咱們能夠進行傻瓜式的開發操做。瀏覽器
看到這裏,咱們不由要問:既然有了ashx爲什麼還要aspx?你們都知道ashx中的ProcessRequest方法須要向請求響應報文中輸出html,而每一個html頁內容有不少,若是每次響應都往裏邊輸出html開發起來會很痛苦(這裏主要是指在若是不借助模板引擎的狀況下),而aspx則起到了相似於於一個模板引擎的做用,幫咱們把html的大致框架定義好了,咱們在開發中就只須要操做每次響應須要更改的內容便可。
(0)假如咱們有如下的名爲FirstPage的一個aspx頁面:
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title>第一個WebForm頁</title> </head> <body> <form id="form1" runat="server"> <div> 哈哈,我是ASP.Net WebForm,下面看個人表演。 <br /> <% for (int i = 0; i < 5; i++) { Response.Write("I am a webform page.<br/>"); } %> <br /> <%= GetServerTime() %> <br /> <asp:TextBox ID="txtDateTime" runat="server"></asp:TextBox> <asp:Button ID="btnGetTime" runat="server" Text="獲取時間" onclick="btnGetTime_Click" /> <br /> <% GetDllInfo(); %> </div> </form> </body> </html>
其後臺代碼.cs文件代碼以下:
namespace WebFormDemo { public partial class FirstPage : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected string GetServerTime() { string result = "服務器時間:" + DateTime.Now.ToString(); return result; } protected void GetDllInfo() { Response.Write("頁面類名稱:"+this.GetType() + "<br/>"); Response.Write("程序集地址:"+this.GetType().Assembly.Location + "<br/>"); Response.Write("父類的名稱:"+this.GetType().BaseType + "<br/>"); Response.Write("程序集地址:"+this.GetType().BaseType.Assembly.Location + "<br/>"); } protected void btnGetTime_Click(object sender, EventArgs e) { txtDateTime.Text = DateTime.Now.ToString(); } } }
(1)CodeBehind:在每一個aspx文件中的頭部,咱們都會看到如下的一句代碼
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="FirstPage.aspx.cs" Inherits="WebFormDemo.FirstPage" %>
其中CodeBehind這個屬性定義了此aspx頁面的專屬後臺代碼文件的名稱,而Inherits這個屬性則定義了此aspx頁面所要繼承的父類的名稱(這也能夠簡單地說明,aspx頁面會單獨生成一個類,與後臺代碼類不重合在一塊兒)。所以,aspx.cs就是aspx的後置處理代碼,負責處理aspx中<%%>和runat="server"的內容。
(2)子類與父類:咱們使用ASP.NET寫的網站在運行時候都會被編譯生成爲一個一個的程序集(.dll),而咱們的aspx頁面也會被生成爲一個一個的類。那麼,咱們如何來證實aspx會生成一個類,並且仍是aspx.cs中的類的子類呢?那麼,咱們須要反編譯系統所生成的程序集(.dll)文件。
第一步:找到網站所生成的程序集
咱們能夠經過寫入如下代碼,而後在aspx中<% GetDllInfo(); %>調用;
protected void GetDllInfo() { Response.Write("頁面類名稱:"+this.GetType() + "<br/>"); Response.Write("程序集地址:"+this.GetType().Assembly.Location + "<br/>"); Response.Write("父類的名稱:"+this.GetType().BaseType + "<br/>"); Response.Write("程序集地址:"+this.GetType().BaseType.Assembly.Location + "<br/>"); }
瀏覽頁面,會顯示如下結果:經過下圖能夠看到,咱們的FirstPage這個頁面會生成一個ASP.firstpage_asx的類,其父類是FirstPage。
PS:當某個頁面第一次被訪問的時候,CLR就會使用一個代碼生成器去解析aspx文件並生成源代碼並編譯,而後之後的訪問就直接調用編譯後的dll,這也是爲何aspx第一次訪問的時候很是慢的緣由。
第二步:反編譯臨時程序集文件
①經過上面顯示的路徑找到dll,並拖到反編譯工具(ILSpy或者Reflector,前者開源免費,後者已經收費,但天朝,你懂的。)進行查看。經過下圖能夠看出,頁面類aspx是後臺代碼類所綁定的子類,它的名稱是aspx文件名加上「_aspx」後綴。所以,這裏也就解釋了爲何在aspx中要訪問的方法必須是public和protected的訪問修飾符才能夠。
②下圖則展現了對頁面後置代碼類所在的程序集進行反編譯的狀況:
第三步:咱們在剛剛時就說了,服務器端對aspx處理的過程是一個渲染生成html的過程,如何來深刻理解這句話,咱們能夠在此藉助反編譯工具來一探究竟。經過對aspx類的反編譯,咱們能夠看到在它的方法列表中有以下幾個命名格式同樣的方法:
①_BuildControl_controlX(); X表明數字
經過對這幾個方法的源碼分析,咱們能夠知道,這些方法都在作一件事件:拼接生成aspx頁面的html內容。每一個方法都會返回一個控件類型的對象,有LiteralControl類型,也有HtmlHead類型(在aspx中只要給head加了runat="server"就會有此類型的生成方法)等等,那麼這些數字又表明了什麼?難道是生成html的執行順序麼?這些生成方法又是在哪一個方法中被一一調用的呢?別急,下面咱們就來看看。
②經過後面幾個方法源碼的查看,咱們發現原來上面的幾個生成控件的方法都在一個叫作BuildControlTree的方法(生成控件樹)中被依次調用。
這裏幾乎是按照數字序號的順序來依次調用具體的BuildControl_controlX()方法,並將每次返回的控件添加到頁面中去。這裏能夠看到,BuildControlTree方法的參數是其自己,它實現了IParserAccessor的接口。這裏暫且將這個接口其理解爲一個大的控件容器,能夠往這個容器裏邊添加子控件(這裏看到不一樣類型的控件均可以往裏邊加,那麼確定初步判定方法參數應該是object類型),這裏將每次調用BuildControl_controlX()方法所返回的控件類型添加到了這個容器中。
③剛剛分析了BuildControlTree方法,知道了控件的生成過程。可是,頁面主體內容又在哪裏呢?服務器端要返回的內容可不止是那些控件的HTML代碼啊。別急,經過查看反編譯的方法,咱們看到原來Renderform1這個方法裏邊。PS:這裏方法名爲何是form1呢?那是由於咱們在aspx中給form表單設置的ID就爲form1。
④這裏咱們就分析到這兒,而WebForm具體的頁面生命週期留到後面的ASP.Net頁面生命週期探索的文章中詳細介紹。這裏咱們只須要知道,aspx這個類是其後置代碼類的子類,它要作的工做就是幫咱們生成要返回瀏覽器端的html內容便可。其中,RenderForm將渲染生成整個form表單,而BuildControlTree則會生成服務器控件樹,以便在後面的方法中方便地調用每一個控件的RenderControl方法生成html字符串。
企業項目中常用到的最多仍是一些「輕量級」的控件,例如:Button、TextBox、CheckBox、RadioButton、DropDownList、Repeater、ListView等;就我所實習的單位來講,這一年作WebForm的項目以來,用的最多也就是這些控件,數據控件除了Repeater就沒用過其餘的。我以爲數據控件的話,好好學習下Repeater就夠了,由於Repeater已經足夠強大了。至於什麼***DataSource、Validator、Wizard、Login還有什麼ASP.Net AJAX ToolKit就根本沒雜用,這些控件既複雜又不實用,並且還比較重量級。
PS:有關Repeater控件的詳細學習,能夠參考w3school的教程:http://www.w3school.com.cn/aspnet/aspnet_repeater.asp
(1)Button控件中的OnClientClick屬性
①在WebForm中,Button控件有兩個Click事件:一個是OnClick的服務端事件,另外一個是OnClientClick的客戶端事件;OnClick事件寫在後置代碼類中,每次點擊Button首先會觸發OnClientClick事件(OnClientClick會返回一個bool值,爲true則繼續執行OnClick,爲false則不繼續)。
②經過分析這個屬性,能夠知道OnClientClick是一個字符串屬性,寫的代碼是JavaScript代碼,在上面所說的BuildControl方法中會渲染成input的onclick方法,它會運行在瀏覽器端。
1 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ClientClickPage.aspx.cs" 2 Inherits="WebFormDemo.ClientClickPage" %> 3 4 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 5 <html xmlns="http://www.w3.org/1999/xhtml"> 6 <head runat="server"> 7 <title>OnClientClick</title> 8 <script type="text/javascript"> 9 function checkConfirm() { 10 var flag = confirm("您肯定要刪除此內容?"); 11 return flag; 12 } 13 </script> 14 </head> 15 <body> 16 <form id="form1" runat="server"> 17 <div> 18 <asp:Label ID="lblFlag" runat="server" Text="This is a label control text."></asp:Label> 19 <asp:Button ID="btnDelete" runat="server" Text="Delete" 20 OnClientClick="return checkConfirm()" onclick="btnDelete_Click" /> 21 </div> 22 </form> 23 </body> 24 </html>
在上面的Button控件中,既設置了OnClientClick也設置了OnClick服務端事件,瀏覽生成的頁面源代碼,能夠看到在生成的html中,OnClientClick確實是渲染成了input的onclick這個瀏覽器端的事件:在Button每次以POST方式向服務器提交請求以前,都會先進行checkConfrim這個方法的判斷,若是返回值爲true纔會將請求提交到服務器端;
(2)被某些人濫用的LinkButton
①LinkButton用法跟Button差很少,區別就只在於LinkButton渲染成超連接(<a></a>),而Button渲染生成input標籤(<input type="button"></input>)。
②不要用LinkButton來實現普通的超連接,在實際開發中,我還真見過有些人用LinkButton來實現超連接的:他們在LinkButton的OnClick事件中寫Response.Redirect("xxx.aspx");這種頁面跳轉代碼,徹底是做死的節奏啊。能在瀏覽器端進行的事兒爲啥要弄到服務器端來進行呢?並且就只是一個頁面跳轉的小事。
(1)什麼是PostBack
好比如今正在訪問a.aspx這個頁面上,點擊頁面上的某個submit按鈕把數據提交到a.asx.cs進行處理,這個過程則能夠看做是:「從客戶端瀏覽器把以前的狀態數據提交回來(PostBack)」。
PS:設置了runat="server"的Button或者input控件都會渲染生成type="submit"的按鈕
(2)剛剛提到只有點擊submit類型的按鈕纔會提交請求到服務器,那麼在如下這種場景如何破呢?
1 <form id="form1" runat="server"> 2 <div> 3 <asp:DropDownList ID="ddlProvince" runat="server" 4 onselectedindexchanged="ddlProvince_SelectedIndexChanged"> 5 <asp:ListItem Value="BJ">北京市</asp:ListItem> 6 <asp:ListItem Value="CQ">重慶市</asp:ListItem> 7 <asp:ListItem Value="SC">四川省</asp:ListItem> 8 </asp:DropDownList> 9 <asp:DropDownList ID="ddlCity" runat="server"> 10 <asp:ListItem Value="-1">請先選擇省份</asp:ListItem> 11 </asp:DropDownList> 12 </div> 13 </form>
有一個省市兩級聯動的下拉列表場景,在用戶選擇一個省份後,自動從服務器獲取屬於該省份的市名下拉列表。這裏使用了DropDownList控件,該控件提供了一個叫作SelectIndexChanged的事件,它會幫咱們渲染生成select的onchange的瀏覽器事件。可是在頁面的瀏覽過程當中,咱們怎麼選擇不一樣的省份,市名稱的下拉列表就是不動,由於沒有向服務器提交數據請求。
①這時候,一位名叫MSDN的大神會告訴你,須要給這個DropDownList控件設置一個AutoPostBack="true"的屬性,經調試後果真可行了。可是,做爲一個學習者,咱們會想知道到底這個AutoPostBack幫咱們作了什麼事兒?這時,咱們能夠經過查看瀏覽器的源代碼一探究竟。
②經過瀏覽器提供的開發人員工具查看數據請求報文,能夠看到除了提交form中的input外,還提交了ASP.Net WebForm預置的一些隱藏字段,而這些隱藏字段則是WebForm爲咱們提供便利的基礎。好比EventTarget則記錄剛剛提交給服務器的是哪一個服務器控件。
注:WebForm頁面中若是有一個runat="server"的form,那麼一定會涉及到IsPostBack。
(1)Http的無狀態:由於Http是無狀態的,因此此次會話結束下次再給我提交請求我也不記得你是誰,即便你是李剛的兒子,老子也不認識。那麼,爲了解決這種問題,咱們可使用一些方法來解決,例如設置一個隱藏字段來判斷,若是是PostBack那麼確定請求報文中會帶上這個字段,若是不是那麼請求報文中確定沒有這個字段。好比,下面咱們使用隱藏字段來做爲判斷PostBack的標誌。
<input name="IsPostBack" type="hidden" value="true" />
(2)ASP.Net WebForm中內置了一個IsPostBack屬性(bool類型),咱們能夠在Page_Load事件中判斷IsPostBack是否爲true,若是不爲true則能夠知道是第一次訪問或者是請求頁面的操做,而若是爲true則表明是PostBack操做,咱們能夠分別進行不一樣的業務邏輯處理。例如:有的代碼只會在頁面第一次加載時才執行(好比從數據庫中讀取數據並顯示),這時就應該使用IsPostBack進行判斷。
if (!IsPostBack) { this.lblInfo.Text = "第一次來,不是PostBack"; } else { this.lblInfo.Text = "非第一次來,是PostBack"; }
(3)經過查看生成的頁面html代碼,咱們沒有發現頁面中有IsPostBack的這個隱藏字段。那麼,它是存儲在哪一個位置又是根據什麼來判斷的呢?實際上,IsPostBack屬性是根據ViewState中的一些特殊的鍵值對來判斷賦值的(由於:每次提交請求後,服務器端都會返回不一樣的ViewState隱藏域給瀏覽器端;一樣,瀏覽器每次也會將ViewState提交給服務器端,服務器端會解析ViewState還原上次狀態)。對於ViewState,能夠經過一些軟件例如ViewStateDecoder進行解析查看,例以下圖:
若是咱們禁用了ViewState,那麼也就沒法正常使用IsPostBack屬性了,也沒法正常使用PostBack了。那麼對於ViewState,我會在下一篇進行簡單探祕,本篇就到此爲止。
若是你以爲本文對你有用,那就麻煩點個「推薦」吧,也能讓我更有動力寫下去,謝謝!
(1)反編譯利器ILSpy:https://github.com/icsharpcode/ILSpy/releases/download/2.2/ILSpy_2.2.0.1706_Binaries.zip
(2)aspx揭祕的WebFormDemo:http://pan.baidu.com/s/1ntqOl4H
(3)服務器控件揭祕WebFormDemo:http://pan.baidu.com/s/1qWFK8cW