ASP.Net WebForm溫故知新學習筆記:1、aspx與服務器控件探祕

開篇:毫無疑問,ASP.Net WebForm是微軟推出的一個跨時代的Web開發模式,它將WinForm開發模式的快捷便利的優勢移植到了Web開發上,咱們只要學會三步:拖控件→設屬性→綁事件,即可以行走於天下。但這樣真的就能夠走一生嗎?實際上,ASP.Net常常被噴的詬病就在於WebForm以及只會拖控件的ASP.Net程序員,每每大型互聯網系統也沒有采用WebForm的模式進行開發。可是,WebForm並非一無可取,而是咱們沒有用好,還有不少東西咱們知其然不知其因此然,如今咱們就來對這些平時所不注意但又十分關鍵的東西一探究竟。javascript

1、神祕不神祕—aspx探祕

1.1 WebForm時代的請求對象

aspx

  在WebForm中,全部的頁面請求都是以aspx文件做爲請求對象(靜態化和僞靜態的除外)。例如上圖中,訪問者在瀏覽器端經過輸入URL:blog/index.aspx向服務器端發送請求,服務器端首先找到這個index.aspx,而後建立頁面對象(index.aspx.cs文件中的類對象),調用這個頁面對象中的ProcessRequest方法和Page_Load方法(在此過程當中,有可能須要訪問數據庫)來生成aspx頁面的全部html內容,最後將生成好的html返回給瀏覽器端。html

  所以,咱們能夠知道,服務器端對aspx的處理過程其實就是一個渲染生成html的過程java

1.2 神奇的<%%>

  經過實踐可知,在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的關係,不要急)數據庫

1.3 aspx與ashx的關係

  ashx是通常處理程序,它是一個實現了IHttpHandler的輕量級處理程序,處理操做都在ProcessRequest方法中完成。後端

  而aspx則至關於一個特殊的、高級的ashx,aspx所對應的父類是System.Web.UI.Page這個類,經過查看Page類的定義,咱們能夠看到Page也實現了IHttpHandler的這個接口。另外之因此說它是高級的ashx,是由於aspx幫咱們封裝了許多底層的操做,使得咱們能夠進行傻瓜式的開發操做。瀏覽器

  看到這裏,咱們不由要問:既然有了ashx爲什麼還要aspx?你們都知道ashx中的ProcessRequest方法須要向請求響應報文中輸出html,而每一個html頁內容有不少,若是每次響應都往裏邊輸出html開發起來會很痛苦(這裏主要是指在若是不借助模板引擎的狀況下),而aspx則起到了相似於於一個模板引擎的做用,幫咱們把html的大致框架定義好了,咱們在開發中就只須要操做每次響應須要更改的內容便可。

1.4 aspx與aspx.cs的關係

  (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>
View Code

  其後臺代碼.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();
        }
    }
}
View Code

  (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字符串。

2、好用很差用—服務器控件探祕

2.1 企業中到底在用哪些控件?

  企業項目中常用到的最多仍是一些「輕量級」的控件,例如: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

2.2 須要注意的基本控件用法

  (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>
View Code

  在上面的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");這種頁面跳轉代碼,徹底是做死的節奏啊。能在瀏覽器端進行的事兒爲啥要弄到服務器端來進行呢?並且就只是一個頁面跳轉的小事。

2.3 AutoPostBack的那點事

  (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>
View Code

  有一個省市兩級聯動的下拉列表場景,在用戶選擇一個省份後,自動從服務器獲取屬於該省份的市名下拉列表。這裏使用了DropDownList控件,該控件提供了一個叫作SelectIndexChanged的事件,它會幫咱們渲染生成select的onchange的瀏覽器事件。可是在頁面的瀏覽過程當中,咱們怎麼選擇不一樣的省份,市名稱的下拉列表就是不動,由於沒有向服務器提交數據請求。

  ①這時候,一位名叫MSDN的大神會告訴你,須要給這個DropDownList控件設置一個AutoPostBack="true"的屬性,經調試後果真可行了。可是,做爲一個學習者,咱們會想知道到底這個AutoPostBack幫咱們作了什麼事兒?這時,咱們能夠經過查看瀏覽器的源代碼一探究竟。

  ②經過瀏覽器提供的開發人員工具查看數據請求報文,能夠看到除了提交form中的input外,還提交了ASP.Net WebForm預置的一些隱藏字段,而這些隱藏字段則是WebForm爲咱們提供便利的基礎。好比EventTarget則記錄剛剛提交給服務器的是哪一個服務器控件。

2.4 爲何須要IsPostBack

  注: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

 

相關文章
相關標籤/搜索