經過前面四節摧殘,現在終於可以鬆了一口氣,開始進入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)本身,而Request是WebFrom的一個屬性(指向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物件的不同之處
Session在ASP時代是相當重要的物件,由於網際網路的特性,因此,每一張網頁基本上是獨立的,儘管在你的專案裡面,可能有a.asp、b.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程式設計師來說,可能是有點不幸的事情,那就是ASP的Session和ASP.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.Text的Session變數,由於我們讓使用者在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個,名稱分別是S1和S2,裡面的值則是S1→『Test』,S2→『Test新機會唷』。
IsPostBack會傳會該物件(此例是Web From)是否為第一次被執行,還是經過PostBack(Submit回來給自己)之後的第二次執行。此屬性是ASP.NET當中相當重要的一個物件屬性,非常重要,請讀者特別注意。
因為我們先前提過,在ASP.NET當中幾乎每一個WebForm在Submit的時候,都是自己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回來),則進行物件初始化動作,其他時候則不進行。
這樣,程式就會依照我們的需求,當使用者按下一次Button,TextBox裡面的值就會加一:
整個Web From動作的流程,可參照下圖:
ViewState和Session非常類似,唯一不同之處,是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 Application、Session和ViewState的比較
當我們了解了ViewState和Session之後,Application也是一個不可不提的重要解角色,Application和Session的意義非常接近,唯一不同之處是Application是跨使用者的。
也就是說,一般的Session變數是針對我們網站的『某一個』使用者,而Application變數則是針對我們網站的『所有』使用者。
例如,當您在程式中設定如下:
Application (“Value”)=20 |
同時間使用我們網站的所有網友,針對同一個Application變數都會讀取(和寫入)同一塊記憶體。這也表示,如果網站上有A、B兩個使用者,當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變數 | Client端HTML碼中 | 低 | 『同一個網頁』來回PostBack時保留資料用 | 網路流量(頻寬) |
從上面的比較您可以得知,ViewState變數只會來來回回的在Server端和Client端傳來傳去(夾在HTML碼裡面),這也是為什麼ViewState變數只在『同一個網頁』中有效的原因。
但Session變數則是透過瀏覽器的Session ID和IIS來溝通,因此針對的是某個使用者。而Application變數則根本不管Session ID,所以變成所有使用者共用。
比價之後,您就可以知道,如果程式中大量使用Application變數和Session變數,將會消耗Server端的記憶體。但如果大量使用ViewState變數,因為都儲存在HTML碼裡面,所以會佔用網際網路傳遞的頻寬。這中間的取捨,就看您的程式需要以及您實際的網路資源而設計。
4-5-8 ASP.NET之Event-Driven網頁設計概念
從上面這幾節中,您大概體驗了一下ASP.NET和ASP在程式設計上最大的不同之處。如果您過去同時也是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,接著我們按下『取出』鈕,您要不要猜猜看,畫面中的TextBox1和TextBox2裡面的值分別是多少?
和您原先想的一樣嗎?按下取出鈕之後,程式會在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關閉重開或是專案重新編譯,才會將值清空。
沒有留言:
張貼留言