2011年2月20日 星期日

4-5 Web Form物件

        經過前面四節摧殘,現在終於可以鬆了一口氣,開始進入ASP.NET程式設計的重點 Web Form 物件』。

        前面提過,『之所以在要花四節的篇幅來介紹整個ASPX運作流程,是因為進入高階的ASP.NET Web應用程式設計領域,得需要瞭解整個ASPX的運作概念,才能夠精準的掌握程式設計的流程,善用Web控制項,並且流利的操作Web Form物件,否則在撰寫Web應用程式的時候,很有可能會循著過去ASP的習慣,把ASP.NET當成ASP來用,這樣將失去ASP.NET的價值,且容易背離Web Form的物件導向程式設計精神。』

        上面這段敘述中,提到了一個在ASP時代和ASP.NET時代相當不同程式設計理念,在ASP中,基本上是以VB Script的程式語言架構來開發,因此實在沒什麼物件導向的架構(也沒有此需要),而且也實在很不容易實作出來。

        但是ASP.NET則是標準的物件導向程式設計架構,我們先看一個空白的Web Form網頁就可以知道。

4-5-1 Web Form網頁的物件導向架構

        當您建立一個Web Form網頁時,標準的空白Web Form頁面會像下面這樣:
        首先您會看到,WebForm1本身是一個類別物件(Class),繼承自System.Web.UI.Page類別,物件導向程式設計架構儼然出現。

        在上圖中,Visual Basic.NET的程式設計環境,可以展開/收回一個程式碼片段,當您展開『Web Form 設計工具產生的程式碼』之後,會出現另外一段程式碼片段,是該Web Form網頁的初始化(Initialize)程式。

備註:
我們應該開始把Web Form網頁視為一個物件,因此,本書後面提到Web Form物件,基本上意義同等於Web Form網頁


        而底下也有網頁的Page_Load程式,Page_Load程式會在網頁載入(也就是觸發了Page_Load事件)時被執行。

        因此,您可以發現,整個Web Form物件(也就是Web Form網頁),在Code Behind程式碼被執行時,是被視為一個物件實體的,既然是一個物件,就會有物件應有的事件(Event)、屬性(Property)、與方法(Method)

        儘管我們過去使用ASP撰寫程式時,ASP本身並沒有物件導向的程式設計架構,但是我們在撰寫ASP程式時,所使用的Request物件、Response物件、或是資料庫操作時的Recordset物件,都具有物件結構。因此,我們假設讀者具備了基礎的物件導向程式設計概念,更深入的物件導向程式設計架構,可參考後面的相關章節。

4-5-2 Me關鍵字與Page_Load事件

 

        既然是Web Form是一個物件,就開始擁有物件本身應有的特性,『Me』關鍵字也就出現了,Me關鍵字代表著Web From物件本身。

        一張APSX網頁即是以一個Web From物件來表示,您會在一個ASPX網頁的程式設計環境中,看到底下的程式碼:
0001:    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
0002:        '在此加入要初始化頁面的使用者程式碼
0003:    End Sub


        上面的程式中,Page_Load是當該網頁被載入的意思。因為ASPX網頁被載入的這個動作,會產生WebForm物件的Page_Load事件,因此,上面程式中的Page_Load事件中,撰寫的程式碼,可以視為:
Web From物件(Page)Load(載入)的這個事件發生時,要執行的程式碼。


我們在程式碼中加上下面這一行:
Response.Write("PhysicalPath:<BR>" & Me.Request.PhysicalPath)


當我們試著執行該網頁時,會出現下面這樣的效果:
        由於我們在程式中寫著
0001:    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
0002:        '在此加入要初始化頁面的使用者程式碼
0003:        Response.Write("PhysicalPath:<BR>" & Me.Request.PhysicalPath)
0004:    End Sub


        使得該網頁(Page)在被載入(Load)的時候,先執行了Response.Write("PhysicalPath:<BR>" & Me.Request.PhysicalPath)這個動作,而『Me』,代表著網頁(Web From)本身,而RequestWebFrom的一個屬性(指向Request物件)PhysicalPath則是Request物件的一個屬性。PhysicalPath會傳回網頁的實體位置。因此執行後顯示出如上圖的結果。

        但是請特別注意,我們試著在瀏覽器中檢視結果網頁的原始HTML碼,發現一件事情,請看網頁執行後傳回的HTML碼,在第一行的部分:
網頁執行後傳回的HTML碼:
0001:PhysicalPath:<BR>c:\inetpub\wwwroot\AspxDemo\Chapter02\Me\WebForm1.aspx
0002:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
0003:<HTML>
0004:    <HEAD>
0005:               <title>WebForm1</title>
0006:               <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.0">
0007:               <meta name="CODE_LANGUAGE" content="Visual Basic 7.0">
0008:               <meta name="vs_defaultClientScript" content="JavaScript">
0009:               <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
0010:    </HEAD>
0011:    <body>
0012:               <form name="Form1" method="post" action="WebForm1.aspx" id="Form1">
0013:<input type="hidden" name="__VIEWSTATE" value="dDwtMTI3OTMzNDM4NDs7PtB6+XUFG1tmPqUpvgneLCKcX4Lz" />
0014:
0015:                          <FONT face="新細明體"></FONT>
0016:               </form>
0017:    </body>
0018:</HTML>


        您會發現,我們在Page_Load事件中寫的程式,所輸出(Response.write)的結果,是在網頁HTML碼的最上方(第一行),而不在<Body>…</Body>標記裡面。

        這代表著,Page_Load事件中所做的任何動作,都會在.NET產生WebForm網頁的HTML碼前被執行。這樣子輸出的網頁,並不符合標準的HTML架構,在IE中顯示沒有問題,但是某些特定的瀏覽器卻無法解讀,因此,我們在ASPX網頁中,幾乎不再像過去ASP程式一樣,使用上面這樣的方式(Response.write)來輸出或產生HTML碼,而取代以物件化的網頁設計方式。

4-5-3 物件化網頁設計方式(不再使用Response.write)

 

        剛才說,我們不再使用Response.write這樣的方式來輸出或產生HTML碼,那網頁上如果真的需要HTML(或要顯示一些特定的文字)時該怎麼辦呢?

        其實,當透過Web控制項來設計Web From之後,幾乎不需要自己用Response.write來輸出或產生HTML碼。因此,實際上需要自行產生HTML碼的機會並不多,但是如果真的需要的時候,ASP.NET還是提供了一個『Literal』物件,幫助我們透過物件化的方式來完成這樣的工作。

        我們後面再介紹literal控制項,我們先看看,像剛才這個例子,要在Web From中顯示一小段文字,不使用Response.write,在ASP.NET的標準設計方式中,該怎麼做呢?

        我們新增一個網頁:
        然後在該網頁上,利用工作箱,將Label拖曳到畫面上,像上圖這般,Label控制項會被自動命名成Label1

        接著,在Web From頁面上的任何空白地方,Double-Click滑鼠左鍵,Visual Basic.NET會切換到程式設計模式,請輸入下面的程式碼:
0001:    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
0002:        '在此加入要初始化頁面的使用者程式碼
0003:        Me.Label1.Text = Now
0004:    End Sub


        還記得我們剛才提過的『Me』關鍵字嗎?

        在第3行中,我們先按下『me.』當打下『.』之後,Visual Basic.NET會自動帶出me(就是Web From物件本身)的所有屬性,而您會看到Label1也在其中:
        這就是Me物件的好用之處。剛才由於我們在Web From上建立了一個Label1物件,因此,當您按下『me.』之後,系統帶出me(Web From)物件中的所有成員(Member),因此也會包括Label1
筆者習慣這麼寫程式,一來很清楚的表達Label1是屬於『Me』的成員,二來寫程式的時候可以稍稍偷一點懶,只需要打Me.就可以選,不用打Label1那麼多字,特別是當Web From上面的物件越來越多時,都不用刻意去記得物件的名字,這時就會感到特別好用。


        您可以建置並執行該範例,出來的執行結果如下圖:
        這時候您會發現,和剛才我們利用Response.write呈現文字時似乎一模一樣,但是請在IE中選擇『檢視』→『原始檔』,您會發現HTML碼如下:
網頁執行後傳回的HTML碼:
0001:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
0002:<HTML>
0003:    <HEAD>
0004:               <title>Default</title>
0005:               <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.0">
0006:               <meta name="CODE_LANGUAGE" content="Visual Basic 7.0">
0007:               <meta name="vs_defaultClientScript" content="JavaScript">
0008:               <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">
0009:    </HEAD>
0010:    <body>
0011:               <form name="Form1" method="post" action="Default.aspx" id="Form1">
0012:<input type="hidden" name="__VIEWSTATE" value="dDwtMTU3ODAzNTQ4MDt0PDtsPGk8M
T47PjtsPHQ8O2w8aTwxPjs+O2w8dDxwPHA8bDxUZXh0Oz47bDwyMDAyLzEwLzI5IOS4i+WniCAwMz
oyODo1NTs+Pjs+Ozs+Oz4+Oz4+Oz6scczrKCkK+epUjTgIbmDRTjgWdA==" />
0013:
0014:                          <FONT face="新細明體">
0015:                                    <span id="Label1" style="width:350px;">2002/10/29 下午 03:28:55</span></FONT>
0016:               </form>
0017:    </body>
0018:</HTML>


        請注意第15行,真正顯示時間的HTML碼在這裡,並非像剛才那個例子,在HTML的第1行就莫名其妙的加了一段Response.Write產生的文字:
之前的那個例子,網頁執行後傳回的HTML碼:
0001:PhysicalPath:<BR>c:\inetpub\wwwroot\AspxDemo\Chapter02\Me\WebForm1.aspx
0002:<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
0003:<HTML>
0004:    <HEAD>
0005:               <title>WebForm1</title>
0010:    </HEAD>
0011:    <body>
0012:               <form name="Form1" method="post" action="WebForm1.aspx" id="Form1">
0013:<input type="hidden" name="__VIEWSTATE" value="dDwtMTI3OTMzNDM4NDs7PtB6+XUFG1tmPqUpvgneLCKcX4Lz" />
0014:
0015:                          <FONT face="新細明體"></FONT>
0016:               </form>
0017:    </body>
0018:</HTML>


        這就是使用Response.Write和使用Web控制項來設計網頁的不同之處。過去我們在ASP時代都會利用Response.Write做網頁內容的呈現,但ASP.NET已經不這麼做了。

        ASP.NET時代,請勿再使用Response.Write來輸出網頁,因為不論您在Code Behind程式碼的任何位置使用Response.Write (不管是否像剛才在Page_Load事件裡,或是在其他地方都一樣),輸出的文字都會在整個HTML網頁的最上方,這是因為Code Behind程式碼是在ASPX網頁傳回IIS前被執行的。這樣的情況,使得Response.Write完全無法和Web From上的Web控制項來配合。

        ASP.NET中,請開始使用物件化的模式來設計網頁,特別是當您寫過幾個物件化的Web應用程式專案之後,就會發現採用物件方式設計的強大與開發效率上的卓越。

        而在物件化網頁設計模式下,也實在無法用Response.Write來配合輸出HTML碼,主要的原因是,以Response.Write輸出時,無法將HTML碼放置到特定的Web控制項上,或是特定的位置,而是一律在網頁的最前面,因此,日後我們大概就不會再提到Response.Write了。

4-5-4 Session物件的不同之處

        SessionASP時代是相當重要的物件,由於網際網路的特性,因此,每一張網頁基本上是獨立的,儘管在你的專案裡面,可能有a.aspb.asp這兩張網頁,在a.asp裡面我們可能對使用者輸入的資料作了一些動作或處理,或是得到一些資訊,但是b.asp並不知道,所以我們必須透過session,作為網頁和網頁之間的溝通橋樑,用來交換資料或資訊。

        例如,我們在本章一開始介紹的登入介面實例中,Login.asp作為使用者輸入帳號密碼,並且判斷帳號密碼是否正確的頁面,當判斷帳號密碼輸入正確之後,我們將session("isLogin")設為True,爾後,同一個使用者(同一個client端瀏覽器連入本伺服器的每一個http request連線,都會被視為同一個使用者,直到Timeout或是關閉瀏覽器,Session才結束)讀取每一個asp網頁時,如果網頁中有呼叫session(“isLogin”),傳出的值都會是True。這樣我們就可以判斷該使用者已經合法的登入過。

注意:每一個連入伺服器的不同的使用者(不同的Client)都會擁有各自的Session變數。亦即,在上例中,當伺服器上同時有兩個使用者登入,這兩個使用者都會擁有各自的Session(“isLogin”),並不會共用。


        如此重要的機制,在ASP.NET當中當然也提供,但是,首先有一件對於過去ASP程式設計師來說,可能是有點不幸的事情,那就是ASPSessionASP.NET中的Session原則上是不相容的。

        也就是說,當您過去用ASP寫了一個程式,如今想升級到.NET Framework平台上,因此將您『部分的』程式改寫成Visual Basic.NET(也就是ASP.NET),基本上,在傳遞Session的時候,ASP所建立的Session變數,會被ASP.NET視為空值,也就是會被當成根本沒有這個參數。

        但是令人稍稍欣慰的事,則是,ASP.NET中的Session使用方式和ASP當中完全一樣,具有在同一個User開啟的所有中,擔任全域變數的感覺。由於表單(網頁)與表單(網頁)之間變數的傳遞,都交給Session來處理,因此Session的使用就相當的重要。

        原則上,Session物件和過去ASP時代操作方式相同,可以存放單純的變數值或是物件,但在ASP.NET時代,由於資料型別變得更多了(ASP時代沒有嚴格的資料型別),因此Session可以容納的變數,也從一般的值、到物件、結構

        我們看下面這個例子,我們在Web From上面建立兩個TextBox,用來輸入Session名稱和值,另外建立一個Label來顯示Session狀態,以及三個按鈕,如下圖所示:
        我們讓使用者在『Session變數名稱』欄位中輸入任意的Session變數名稱,然後在『Session變數內容』裡面設定變數的值,當使用者按下『儲存』,會將值儲存在該Session變數中,按下『取回』鈕時則取回變數內容,另外,『顯示所有Session變數狀況』按鈕,則可顯示使用者所有的Session

程式碼如下:
完整範例請參考aspxDemo\Chapter2\Session
0001:    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
0002:        If Me.SessionName.Text = "" Then Exit Sub
0003:
0004:        '儲存Session物件
0005:        Me.Session(Me.SessionName.Text) = Me.SessionValue.Text
0006:    End Sub
0007:
0008:    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
0009:        If Me.SessionName.Text = "" Then Exit Sub
0010:
0011:        '取回Session物件
0012:        Me.SessionValue.Text = Me.Session(Me.SessionName.Text)
0013:    End Sub
0014:
0015:    Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
0016:        Dim i As Integer
0017:
0018:        '顯示目前所有的Session個述
0019:        SessionStatus.Text = "Session Count:" & Me.Session.Count & "<br>"
0020:        SessionStatus.Text += "Sessions:" & "<br>"
0021:
0022:        '列舉每一個Session變數
0023:        For i = 0 To Session.Count - 1
0024:            '顯示每一個Session的名稱和內容
0025:            SessionStatus.Text += Session.Keys(i).ToString & "(" & Session(i) & ")" & "<br>"
0026:        Next
0027:    End Sub

        我們看實際的執行結果,當第一次執行時,畫面上應該是空白的,因此我們在欄位中輸入S1,作為Session變數名稱,『Test』則是Session(“S1”)的值,輸入完成後按下『儲存』:
        儲存完後畫面上沒有任何的動靜,如何確定該Session(“S1”)已經儲存了我們輸入的值呢?

        請清除原先的Session變數內容欄位,然後按下『取回』鈕,當我們按下取回之後,您會發現,原本空白的欄位中變成我們剛才輸入的Session值:
        如果您按下『顯示所有Session變數狀況』,程式則會在Label物件SessionStatus中填入目前所有Session的狀況。

        我們看程式的部分,儲存和取回Session的動作,是透過底下的程式來完成的:
0004:        '儲存Session物件值
0005:        Me.Session(Me.SessionName.Text) = Me.SessionValue.Text
0011:        '取回Session物件值
0012:        Me.SessionValue.Text = Me.Session(Me.SessionName.Text)


        Me.Session當然代表著這張網頁上的Session物件,忽略me亦可,過去我們在ASP中常用Session(“變數名稱”)表示一個Session變數,現在在ASP.NET中,同樣是這樣的用語法。

        Me.Session(Me.SessionName.Text) 則是名稱為Me.SessionName.TextSession變數,由於我們讓使用者在SessionName中自行輸入Session變數名稱,當使用者在SessionName中輸入『S1』,則會將SessionValue.Text的值寫入Session(“S1”),同樣的,Session變數的取回也是採類似的架構。

        有趣的是,當我們建立了很多的Session變數之後,可以透過Session.Keys(i).ToString(範例程式第25)列舉出記憶體中,這位使用者每一個Session變數的名稱,亦可用Me.Session.Count(19)來取得目前該User執行的Web應用程式中,已經建立的Session變數之個數:
0018:        '顯示目前所有的Session個述
0019:        SessionStatus.Text = "Session Count:" & Me.Session.Count & "<br>"
0020:        SessionStatus.Text += "Sessions:" & "<br>"
0021:
0022:        '列舉每一個Session變數
0023:        For i = 0 To Session.Count - 1
0024:            '顯示每一個Session的名稱和內容
0025:            SessionStatus.Text += Session.Keys(i).ToString & "(" & Session(i) & ")" & "<br>"
0026:        Next


        當使用者按下『顯示所有Session變數狀況』鈕時,會執行上面那段程式,這樣執行效果如下圖:
        Label1中,顯示出該使用者的Session變數共有2個,名稱分別是S1S2,裡面的值則是S1→Test』,S2→Test新機會唷』。


        IsPostBack會傳會該物件(此例是Web From)是否為第一次被執行,還是經過PostBack(Submit回來給自己)之後的第二次執行。此屬性是ASP.NET當中相當重要的一個物件屬性,非常重要,請讀者特別注意。

        因為我們先前提過,在ASP.NET當中幾乎每一個WebFormSubmit的時候,都是自己Submit給自己,因此,如果不區分出該網頁是否第一次被執行,還是其實已經執行過了,而是Submit回來的,將會造成很大的困擾!

        我們看下面這個例子:
        我們在Web From上面佈置兩個物件,一個TextBox,一個Button,當使用者按下Button時,則會取出TextBox中的值加一,該Web From的程式碼如下,理論上,我們期待使用者執行這個程式時,每按一次按鈕,TextBox裡面的數自就會加一:
0001:    '物件初始化程式
0002:    Function InitObj()
0003:        Me.txtDate.Text = "1"
0004:    End Function
0005:
0006:    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
0007:        '在此加入要初始化頁面的使用者程式碼
0008:        Me.InitObj()
0009:    End Sub
0010:
0011:    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
0012:        Me.txtDate.Text = Me.txtDate.Text + 1
0013:    End Sub


        11-13行是Button被按下時的程式,將txtDate.Text的值取出後,再存回txtDate.Text。這段程式是正確的,但是當這個Web From實際執行時,無論您怎麼按頁面上的那個按鈕,TextBox中的數值永遠都是『2』,原因很簡單,問題出在第8行的『InitObj』身上,該函式『InitObj』執行一個物件初始化的動作,我們在Page_Load時,將TextBox內的預設為1,讓使用者在第一次執行該網頁的時候看到的TextBox中的值就是1,然後按下按鈕,累加成為2,3,4,5,6…

        這個程式的邏輯也沒錯,物件化程式設計確實應該採用這樣的方式,如果您過去撰寫過VB6.0,整個程式運作的邏輯也是這樣。
         
        但是壞就壞在Web應用程式和Windows應用程式有一個本質上的不同,那就是Web應用程式中的Web From,在每次被呼叫載入時,都會觸發Page_Load事件。

註:
即使使用者在Web From中按下某個按鈕,使得Web From本身Submit回給自己,就算重新載入;在瀏覽器上的網址列輸入網址,按下Enter也算是重新載入,因此都會觸發Page_Load事件。


        因此,Web應用程式不像Windows應用程式。用VB6.0之類的語言所撰寫的Windows應用程式,只有在表單(Windows Form)被載入的時候,才觸發Form_Load事件,至於爾後的動作,不管是使用者在Windows Form上面按下任何按鈕或是物件,都不會再觸發Form_Load(因為表單沒有重新載入),只會觸發與使用者動作相關的事件,例如按下某個按鈕,就觸發Button1_Click事件等。

        可是,Web應用程式本質上是一張網頁,當使用者按下某個按鈕時,就會產生一個Submit動作,該動作導致Web From重新reload,因此在觸發了Button1_Click之類的按鈕事件之外,當然還會觸發Page_Load事件,這點和Windows應用程式相當不同。

註:
Web應用程式必須要這麼設計,因為,不像Windows應用程式,當Web From網頁傳遞到Client端使用者的瀏覽器上時,使用者按下了某個按鈕,後端ASP.NET程式並不知道使用者按下了。因此,必須讓網頁Submit回來,才能夠透過後端的ASP.NET程式,判斷使用者按下了哪個按鈕,或是觸動了哪個動作,然後執行程式碼中相對應的功能,再把結果傳送回Client端,但是這個動作就導致我們剛才提到的狀況,網頁被Reload,因此當然觸發Page_Load事件。


        所以,IsPostBack就擔當了重要角色,它可以告知我們該網頁是不是因為使用者觸動了某個Web From上面的物件,產生了相關的事件,而造成的Reload動作;還是該網頁是使用者第一次執行所觸發的Page_Load動作。我們透過IsPostBack傳出的值,就可以判斷該網頁被Reload的原因(也就是Page_Load事件被觸發的原因)
0006:    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
0007:        '在此加入要初始化頁面的使用者程式碼
0008:        If Not Me.IsPostBack Then Me.InitObj()
0009:    End Sub


        我們將第8行改為上面這樣,如果Web From網頁的IsPostBack屬性傳回False(代表著是第一次執行,而非Submit回來),則進行物件初始化動作,其他時候則不進行。

        這樣,程式就會依照我們的需求,當使用者按下一次ButtonTextBox裡面的值就會加一:
        整個Web From動作的流程,可參照下圖:


        ViewStateSession非常類似,唯一不同之處,是Session是跨網頁的,而ViewState則僅能在該網頁本身保留資訊。

        ViewState的語法為:
'設定
me.ViewState("自己取的名稱")=數值
'取回
變數=me.ViewState("自己取的名稱")


        我們在一個Web From上面佈置兩個TextBox,和三個按鈕,如下圖:

        接著我們撰寫下面的程式碼:
完整範例請參考Chapter02/ViewState/Default.aspx
0001:    '儲存 ViewState
0002:    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
0003:        Me.ViewState(Me.ViewStateName.Text) = Me.ViewStateValue.Text
0004:    End Sub
0005:
0006:    '取回 ViewState
0007:    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
0008:        Me.ViewStateValue.Text = Me.ViewState(Me.ViewStateName.Text)
0009:    End Sub
0010:
0011:    '清空 TextBox
0012:    Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
0013:        Me.ViewStateValue.Text = ""
0014:        Me.ViewStateName.Text = ""
0015:    End Sub


        當您執行該網頁時可以發現,我們利用ViewState一樣可以保留程式中的資訊。
        我們先在ViewState名稱處輸入P1,填入100,然後按下儲存鈕,這時候ViewState(“P1”)將會被設為100

        接著按下清空鈕,然後在ViewState名稱處再輸入P1,按下取回,果然,數值就是剛才我們輸入的100

        您可以從這個範例中,發現ViewState的存取方式與Session真的完全一樣,唯一不同之處就在兩者之間的生命週期。雖然Session是跨網頁的,而ViewState則僅能在該網頁行程本身保留資訊,但並不表示ViewState用處較小,因為Session非常耗費系統資源,而ASP.NET的網頁設計中其實非常多機會要在同一個網頁中保留變數。

        記得嗎?我們提過,Web From的設計架構使得網頁本身時常需要將網頁Submit回給自己,這種動作稱為PostBack,但PostBack之後,網頁內的變數就不見了(因為整個網頁被Reload),這時候時常造成程式設計人員的困擾,因此,為了保留該網頁中的變數值,ViewState就是一個更優於Session的選擇。

        因為不僅ViewState比起Session佔用的系統資源較少,更由於ViewState本身無法垮網頁(session可跨網頁使用)的特性,不會有名稱重複的困擾,因此比Session更適合作為『同一個網頁』保留變數值的工具。

附帶說明,Session較適合作為『不同網頁』之間『資料交換』的工具。而ViewState則是『同一個網頁』之間『資料保留』的工具。


4-5-7 ApplicationSessionViewState的比較

        當我們了解了ViewStateSession之後,Application也是一個不可不提的重要解角色,ApplicationSession的意義非常接近,唯一不同之處是Application是跨使用者的。

        也就是說,一般的Session變數是針對我們網站的『某一個』使用者,而Application變數則是針對我們網站的『所有』使用者。

        例如,當您在程式中設定如下:
Application (“Value”)=20


        同時間使用我們網站的所有網友,針對同一個Application變數都會讀取(和寫入)同一塊記憶體。這也表示,如果網站上有AB兩個使用者,當A使用者設定Application (“Value”)=20,這時候B使用者如果讀取Application (“Value”),則值也會是20

        但是Session變數則不同了,如果當A使用者設定Session (“Value”)=20,這時候同時間在網站上的B使用者如果讀取Session (“Value”),則會是Nothing

        這是因為IIS和瀏覽器之間,會針對不同的使用者設定一個Session ID,每一個新開出來的瀏覽器,都會有一個自己的Session ID,而Session變數是depend on這個Session ID的。

        兩者之間唯一的相同之處,是Session變數和Application變數,都是儲存在Server端,瀏覽器(傳到Client端的HTML)裡面看不到Session變數和Application變數的值。

        但是我們剛才提過的ViewState就不同了,如果我們使用了ViewState變數,然後開啟 (傳到Client端的HTML),您會發現類似下面的HTML片段:
        這是什麼?這就是ViewState的內容,當然,WebForm幫我們編碼過了。否則還得了,全世界都知道我們傳來傳去的是什麼資訊。(不過儘管如此,ViewState的安全性依舊較低)

我們比較這幾種資訊如下:

儲存於
安全性
適用於
佔用資源
Application變數
Server
網站的所有使用者交換資料用,『同一個網站的任何網頁』取得的資料都相同。
Server端記憶體
Session變數
Server
『同一個使用者』在不同網頁上交換資料用
Server端記憶體
ViewState變數
ClientHTML碼中
『同一個網頁』來回PostBack時保留資料用
網路流量(頻寬)


        從上面的比較您可以得知,ViewState變數只會來來回回的在Server端和Client端傳來傳去(夾在HTML碼裡面),這也是為什麼ViewState變數只在『同一個網頁』中有效的原因。

        Session變數則是透過瀏覽器的Session IDIIS來溝通,因此針對的是某個使用者。而Application變數則根本不管Session ID,所以變成所有使用者共用。

        比價之後,您就可以知道,如果程式中大量使用Application變數和Session變數,將會消耗Server端的記憶體。但如果大量使用ViewState變數,因為都儲存在HTML碼裡面,所以會佔用網際網路傳遞的頻寬。這中間的取捨,就看您的程式需要以及您實際的網路資源而設計。

4-5-8 ASP.NETEvent-Driven網頁設計概念

        從上面這幾節中,您大概體驗了一下ASP.NETASP在程式設計上最大的不同之處。如果您過去同時也是VB6.0或是C++程式設計師,大概對於物件導向的Event-Driven(事件驅動)程式設計概念相當熟悉,但是純種的ASP程式設計師恐怕就沒那麼幸運了。

        簡單的說,Event-Driven程式設計概念相當簡單,就是,當使用者觸動了網頁上的某個物件(例如按下 按鈕、在 TextBox中按下Enter、在 RadioButton或是 CheckBox上做了勾選的動作),我們的程式就進行某種特定的相對應動作。
所以您會發現,之前我們寫的每一個範例程式,都是在將程式碼寫在Button_Click事件(就是按下Button之後,觸發的事件)的程序中,例如:
0011:    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
0012:        Me.txtDate.Text = Me.txtDate.Text + 1
0013:    End Sub


        而程式碼中的動作,也都透過物件和物件之間的合作關係來完成,這就是Event-Driven程式設計概念,在整個Windows程式設計架構中,都是以此種概念來進行的。

        只是由於過去ASP程式語言的架構太簡單,無法讓我們透過這種模式來撰寫程式。因此,我們必須不辭辛勞的自己在程式碼裡面使用Request.Form(“xxx”)取出從上一個網頁Submit過來的參數,來判斷使用者在物件中輸入的值,或是按了哪一個按鈕等。而今在ASP.NET中,我們完全不需要(也不建議)這麼做。因此Request物件的使用時機,大大的與過去不同了。

4-5-9 .vb模組檔案』與類似Application的全域變數?

        ASP.NET中開發Web應用程式還有一個挺優的好處,就是整個程式的架構比起過去ASP時代結構化了許多,過去您可能在ASP裡面使用過類似『#include file』之類的指令,將寫在其他地方的程式碼透過Include加入網頁中,但是在Visual Basic.NET中,我們卻建議您採用.vb的模組檔(或是用我們後面將會介紹的『使用者自訂控制項』更好)

        我們現在看下面這個例子,我們建立一個WebForm,包含兩個TextBox和三個按鈕,您可以在TextBox中輸入數字,按下『儲存』鈕,程式會將TextBox中的數值儲存起來(存到變數裡),按下『清空』鈕,則會將TextBox設為0,按下『取出』鈕,則會從變數中將數值取出:

        我們接著方案總管中選擇『加入』→『加入新項目』之後,會出現下列的畫面:

        您可以在選擇『模組』項目之後,按下『開啟』建立一個新的模組檔:
0001:    Public Num1 As Integer '請注意,我是全域變數
0002:
0003:    '取得變數
0004:    Function GetNum() As Integer
0005:        GetNum = Num1
0006:    End Function
0007:
0008:    '儲存變數
0009:    Function SetNum(ByVal n As Integer)
0010:        Num1 = n
0011:    End Function


        模組檔案會是空的,我們在其中加入一個『Num1』變數,並且撰寫上面的程式碼。

        接著我們會到Web From主畫面的程式碼設計環境中,輸入下列程式碼:
0001:    '儲存
0002:    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click, Button3.Click
0003:        Static Num1 As Integer
0004:
0005:        Select Case sender.text
0006:            Case "儲存"
0007:                Num1 = Me.TextBox1.Text
0008:                SetNum(Me.TextBox2.Text)
0009:                Response.Write("Saved OK.")
0010:
0011:            Case "取出"
0012:                Me.TextBox1.Text = Num1
0013:                Me.TextBox2.Text = GetNum()
0014:                Response.Write("Read OK.")
0015:        End Select
0016:
0017:    End Sub
0018:
0019:    '清空
0020:    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
0021:        Me.TextBox1.Text = 0
0022:        Me.TextBox2.Text = 0
0023:    End Sub


        上面這段程式,是使用者按下頁面上的按鈕,將會觸發的Click事件。我們用同一段程式處理Button1.Click(儲存) Button3.Click(取出)這兩個按鈕,主要是為了測試Static型態的變數,在程式中的生命週期。

        請注意第8和第13行,程式中呼叫的內容是寫在『Module1.vb』裡面的副程序。至於『Button2_Click』只是很單純的將TextBox清空。

        您可以試著執行此程式,第一次執行的時候,會是空白的畫面:

        請在兩個TextBox中分別輸入『123』和『456』,然後按下『儲存』鈕。當您按下儲存鈕的時候,會執行到底下的程式:
SetNum(Me.TextBox2.Text)


        這段程式呼叫『Module1.vb』裡面的副程序SetNum()SetNum會將TextBox2.Text的值寫入在『Module1.vb』裡面第1行定義的全域Num1變數。同樣的第7行會將TextBox1.Text的值寫入Static型態的區域變數Num1中。

        然後我們再按下『清空』鈕,程式會將畫面上的兩個TextBox都清為0,接著我們按下『取出』鈕,您要不要猜猜看,畫面中的TextBox1TextBox2裡面的值分別是多少?

        和您原先想的一樣嗎?按下取出鈕之後,程式會在Default.aspx的第17,18行,透過『Me.TextBox1.Text = Num1』和『Me.TextBox2.Text = GetNum()』取回剛才儲存在記憶體內的變數,但是結果卻像您上面看到的畫面,TextBox1中的數值是0,也就是說,儘管我們在Default.aspx中的第3行將Num1定義成Static型態,該數值在網頁Reload(因為在Web From中,按下Button鈕網頁就會Reload)之後,依舊變成空值。

        但是定義在Module1.vb中的全域變數Num1卻不受影響,依舊保有原先的值。您可以試試看將Default.aspx中,原先在『Button1_Click』副程序中定義的Num1變數,改成定義在『Button1_Click』副程序外,成為Default.asp中的全域型態變數:
0001:    Dim Num1 As Integer
0002:    '儲存
0003:    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click, Button3.Click
0004:
0005:        Select Case sender.text
0006:            Case "儲存"
0007:                Num1 = Me.TextBox1.Text
0008:                SetNum(Me.TextBox2.Text)
0009:                Response.Write("Saved OK.")
0010:
0011:            Case "取出"
0012:                Me.TextBox1.Text = Num1
0013:                Me.TextBox2.Text = GetNum()
0014:                Response.Write("Read OK.")
0015:        End Select
0016:
0017:    End Sub


        但是儘管如此,執行的結果依舊一樣,除了定義在Module1.vb中的全域變數不受網頁Reload影響之外,定義在Default.aspx中的任何變數,在網頁Reload之後都會被清空。

        更特別的是,即使您將瀏覽器關掉重開,然後直接按下『取出』鈕,您會發現剛才我們儲存在Module1.vb的全域變數Num1中的值,依舊可以讀取出來,換句話說,定義在Module1.vb中的全域變數補僅不受網頁Reload影響,甚至不受Session中斷(瀏覽器關閉)的影響,您還會發現,同時間若是有別的電腦開啟該網頁,直接按下『取出』鈕,也會得到一樣的數值,因此,定義在Module1.vb中的全域變數生命週期高過所有變數,它的地位根本和過去ASP中的Application一樣,非得將IIS關閉重開或是專案重新編譯,才會將值清空。
 

沒有留言:

張貼留言