介紹ASP.NET服務(wù)器控件之視圖狀態(tài)
ASP.NET服務(wù)器控件是一種服務(wù)器端組件,它封裝了用戶(hù)界面及其相關(guān)的功能。ASP.NET 服務(wù)器控件直接或間接地從 System.Web.UI.Control 類(lèi)派生。ASP.NET 服務(wù)器控件的超集包括 Web 服務(wù)器控件、HTML 服務(wù)器控件(基礎(chǔ)控件)、數(shù)據(jù)控件和 ASP.NET 移動(dòng)控件。
為Web頁(yè)面及其控件保持狀態(tài)信息是非常有必要的。然而,由于Web應(yīng)用程序創(chuàng)建于HTTP協(xié)議的頂層,這是一個(gè)無(wú)狀態(tài)的協(xié)議,因此,保持狀態(tài)信息則變得非常困難。為了解決這個(gè)問(wèn)題,ASP.NET 2.0技術(shù)提供了多種解決方案,例如,利用Session、Cookie、視圖狀態(tài)、控件狀態(tài)、隱藏域、查詢(xún)字符串、個(gè)性化用戶(hù)配置(Profile)等等。對(duì)于利用ASP.NET 2.0技術(shù)創(chuàng)建服務(wù)器控件而言,保持狀態(tài)信息也是非常重要的,其主要解決途徑是利用視圖狀態(tài)和控件狀態(tài)。本文詳細(xì)講解了視圖狀態(tài)(ViewState)的基本知識(shí),并通過(guò)典型應(yīng)用介紹視圖狀態(tài)的應(yīng)用方法。
視圖狀態(tài)概述
視圖狀態(tài)是一項(xiàng)非常重要的技術(shù),它能使得頁(yè)面和頁(yè)面中的控件在從服務(wù)器到客戶(hù)端,再?gòu)目蛻?hù)端返回的往返過(guò)程中保持狀態(tài)信息。這樣就可以在Web這種無(wú)狀態(tài)的環(huán)境之上創(chuàng)建一個(gè)有狀態(tài)并持續(xù)執(zhí)行的頁(yè)面效果。本節(jié)主要介紹有關(guān)視圖狀態(tài)的運(yùn)行機(jī)制、應(yīng)用方法、存儲(chǔ)的數(shù)據(jù)類(lèi)型、性能和安全性、視圖狀態(tài)分塊(這是ASP.NET 2.0的新特性)和優(yōu)缺點(diǎn)等內(nèi)容。
(1)運(yùn)行機(jī)制
視圖狀態(tài)的具體運(yùn)行過(guò)程為:每當(dāng)用戶(hù)請(qǐng)求某個(gè).aspx頁(yè)面時(shí),.NET框架首先把相關(guān)控件的狀態(tài)數(shù)據(jù)序列化成一個(gè)字符串,然后,將其做為名為_(kāi)_VIEWSTATE的隱藏域的Value值發(fā)送到客戶(hù)端。如果頁(yè)面是第一次被請(qǐng)求,那么服務(wù)器控件也將是被第一次執(zhí)行時(shí),名為_(kāi)_VIEWSTATE的隱藏域中只包含控件的默認(rèn)信息,通常為空或者null。在隨后的回送事件中,ViewState中就保存了服務(wù)器控件在前面回送中可用的屬性狀態(tài)。這樣服務(wù)器控件就可以監(jiān)視在當(dāng)前被處理的回送事件發(fā)生之前的狀態(tài)了。這些過(guò)程是由.NET框架負(fù)責(zé)的,對(duì)用戶(hù)來(lái)說(shuō)是執(zhí)行.aspx頁(yè)面就有了持續(xù)執(zhí)行的效果。
(2)存儲(chǔ)的數(shù)據(jù)類(lèi)型
視圖狀態(tài)可以存儲(chǔ)多種類(lèi)型的數(shù)據(jù),并且為了提高運(yùn)行效率,視圖狀態(tài)自身還包括一套已經(jīng)優(yōu)化的針對(duì)常用類(lèi)型的序列化方式。視圖狀態(tài)序列化方式默認(rèn)支持的數(shù)據(jù)類(lèi)型包括以下幾種:String、Int32、Unit、Color、Array、ArrayList、HashTable和自定義類(lèi)型轉(zhuǎn)換器TypeConverter。
視圖狀態(tài)已經(jīng)為Array、ArrayList和包含上面列出類(lèi)型的HashTable對(duì)象進(jìn)行了優(yōu)化。因此,當(dāng)在控件中使用視圖狀態(tài)時(shí),應(yīng)該試著限定于使用以上簡(jiǎn)單數(shù)據(jù)類(lèi)型,以及經(jīng)過(guò)優(yōu)化的類(lèi)型。在此,需要重點(diǎn)說(shuō)明一下自定義類(lèi)型轉(zhuǎn)換器TypeConverter,它提供了一種將值的類(lèi)型轉(zhuǎn)換為其他類(lèi)型以及訪問(wèn)標(biāo)準(zhǔn)值和子屬性的統(tǒng)一方法。例如,可以利用TypeConverter將字符串轉(zhuǎn)換為數(shù)值,或者將數(shù)值轉(zhuǎn)換為字符串。如果沒(méi)有類(lèi)型轉(zhuǎn)換器,那么頁(yè)面框架會(huì)使用.NET框架提供的二進(jìn)制序列化功能來(lái)序列化對(duì)象,這個(gè)過(guò)程是非常耗費(fèi)資源的。
(3)性能和安全性
使用視圖狀態(tài)時(shí),對(duì)象必須先序列化,然后再通過(guò)回傳進(jìn)行反序列化。因此,我們必須了解有關(guān)ViewState性能的內(nèi)容。默認(rèn)情況下,控件的ViewState將被啟用,如果不需要使用ViewState,最好還是將它關(guān)閉。以下情況將不再需要ViewState:(1)控件未定義服務(wù)器端事件(這時(shí)的控件事件均為客戶(hù)端事件且不參加回送的);(2)控件沒(méi)有動(dòng)態(tài)的或數(shù)據(jù)綁定的屬性值。關(guān)閉視圖狀態(tài)的方法是將控件的EnableViewState的值設(shè)置為"false",即EnableViewState="false"。
默認(rèn)情況下,視圖狀態(tài)的有關(guān)內(nèi)容在編譯運(yùn)行發(fā)送給客戶(hù)端時(shí),讀者將在頁(yè)面的HTML代碼中看到__VIEWSTATE隱藏域內(nèi)容。這是一些沒(méi)有意義的字符串,是.NET框架通過(guò)Base64位編碼對(duì)相關(guān)內(nèi)容編碼的結(jié)果。它們是通過(guò)明文方式在客戶(hù)端和服務(wù)器端之間往返傳送。在某些情況下,例如涉及密碼、賬號(hào)、連接字符串等敏感內(nèi)容時(shí),使用默認(rèn)方式是很不安全的。為此,.NET框架為ViewState提供了兩種安全機(jī)制:
A、校撿機(jī)制
可以通過(guò)設(shè)置EnableViewStateMAC="true"屬性來(lái)指示.NET框架向ViewState數(shù)據(jù)中追加一個(gè)散列碼(該散列碼是一種SHA1類(lèi)型,長(zhǎng)度有160位,因此會(huì)嚴(yán)重影響執(zhí)行性能)。在回傳事件發(fā)生時(shí),將重新建立該散列碼,它必須和最初的散列碼匹配。通過(guò)這種方式,能夠有效檢驗(yàn)ViewState是否在傳送過(guò)程中能夠被篡改。默認(rèn)情況下,.NET框架使用SHA1算法來(lái)生成ViewState散列代碼。此外,也可以通過(guò)在machine.config文件中設(shè)置<machineKey>來(lái)選擇 MD5 算法,如下所示:<machineKey validation="MD5" />。MD5算法的性能要比SHA1算法好一些,但是同樣不夠安全。
B、 加密機(jī)制
使用加密來(lái)保護(hù)ViewState字段中的實(shí)際數(shù)據(jù)值。首先,必須如上所述設(shè)置EnableViewStatMAC="true"。然后,將machineKey validation類(lèi)型設(shè)置為3DES,即
- <machineKey validationKey="AutoGenerate" decryptionKey="AutoGenerate" validation="3DES" />
這指示ASP.NET使用3DES加密算法來(lái)加密ViewState值。
(4)視圖狀態(tài)分塊
以上內(nèi)容介紹了視圖狀態(tài)的一些基本知識(shí)。然而,可能部分讀者會(huì)有些疑惑:如果在某些情況下,視圖狀態(tài)數(shù)據(jù)變得很大,那怎么辦呢?這樣顯然會(huì)出現(xiàn)一些意想不到的后果。為此,ASP.NET 2.0新增了一種名為"視圖狀態(tài)分塊"的功能。如果視圖狀態(tài)的數(shù)據(jù)量變得太大,視圖狀態(tài)分塊自動(dòng)將數(shù)據(jù)分成多個(gè)塊區(qū),并將這些數(shù)據(jù)放在多個(gè)隱藏形式的字段中。
若要啟用視圖狀態(tài)分塊,可將MaxPageStateFieldLength屬性設(shè)置為在單個(gè)視圖狀態(tài)字段中允許的最大大小(以字節(jié)為單位)。當(dāng)該頁(yè)回發(fā)到服務(wù)器時(shí),該頁(yè)會(huì)在頁(yè)初始化階段分析視圖狀態(tài)字符串,并還原頁(yè)中的屬性信息。默認(rèn)設(shè)置是-1,這表示不存在最大大小,不會(huì)將視圖狀態(tài)分成多個(gè)塊區(qū)。
(5)優(yōu)點(diǎn)和缺點(diǎn)
使用視圖狀態(tài)具有以下3個(gè)優(yōu)點(diǎn):
一、耗費(fèi)的服務(wù)器資源較少(與Application、Session相比)。因?yàn)?,視圖狀態(tài)數(shù)據(jù)都寫(xiě)入了客戶(hù)端計(jì)算機(jī)中。
二、易于維護(hù)。默認(rèn)情況下,.NET系統(tǒng)自動(dòng)啟用對(duì)控件狀態(tài)數(shù)據(jù)的維護(hù)。
三、增強(qiáng)的安全功能。視圖狀態(tài)中的值經(jīng)過(guò)哈希計(jì)算和壓縮,并且針對(duì)Unicode實(shí)現(xiàn)進(jìn)行編碼,其安全性要高于使用隱藏域。
使用視圖狀態(tài)具有以下3個(gè)缺點(diǎn):
一、性能注意事項(xiàng)。由于視圖狀態(tài)存儲(chǔ)在頁(yè)本身,因此如果存儲(chǔ)較大的值,即使在視圖狀態(tài)分塊的情況下,用戶(hù)顯示頁(yè)和發(fā)送頁(yè)時(shí)的速度仍然可能減慢。
二、設(shè)備限制。移動(dòng)設(shè)備可能沒(méi)有足夠的內(nèi)存容量來(lái)存儲(chǔ)大量的視圖狀態(tài)數(shù)據(jù)。因此,移動(dòng)設(shè)備上的服務(wù)器控件時(shí),將使用其他的實(shí)現(xiàn)方法。
三、潛在的安全風(fēng)險(xiǎn)。視圖狀態(tài)存儲(chǔ)在頁(yè)上的一個(gè)或多個(gè)隱藏域中。雖然視圖狀態(tài)以哈希格式存儲(chǔ)數(shù)據(jù),但它可以被篡改。如果直接查看頁(yè)輸出源,可以看到隱藏域中的信息,這導(dǎo)致潛在的安全性問(wèn)題。
#p#
典型應(yīng)用
在利用ASP.NET 2.0技術(shù)進(jìn)行服務(wù)器控件開(kāi)發(fā)過(guò)程中,有很多方面可以用到視圖狀態(tài)。常見(jiàn)的是利用ViewState字典實(shí)現(xiàn)服務(wù)器控件屬性。ViewState是System.Web.UI.StateBag類(lèi)型-一個(gè)鍵/值對(duì)的字典,服務(wù)器控件的屬性值可以存儲(chǔ)在ViewState中。下面通過(guò)一個(gè)典型示例,說(shuō)明ViewState的應(yīng)用方法。
在自定義服務(wù)器控件LabelInViewState中,實(shí)現(xiàn)了兩個(gè)屬性Text和TextInViewState。前者使用私有變量創(chuàng)建,后者使用ViewState實(shí)現(xiàn)。它們都用于獲取或者設(shè)置文本內(nèi)容。自定義控件實(shí)現(xiàn)文件LabelInViewState.cs源代碼如下所示。
- using System;using System.Collections.Generic;
- using System.ComponentModel;using System.Text;
- using System.Web;
- using System.Web.UI;
- using System.Web.UI.WebControls;namespace WebControlLibrary{
- [DefaultProperty("Text")]
- [ToolboxData("<{0}:LabelInViewState runat=server></{0}:LabelInViewState>")]
- public class LabelInViewState : WebControl {
- private string _text; //實(shí)現(xiàn)Text屬性
- public string Text {
- get {
- return (_text == null) ? string.Empty : _text;
- }
- set { _text = value; }
- }
- //使用ViewState實(shí)現(xiàn)TextInViewState屬性
- public string TextInViewState {
- get {
- String s = (String)ViewState["TextInViewState"];
- return ((s == null) ? String.Empty : s);
- }
- set { ViewState["TextInViewState"] = value; }
- }
- // 重寫(xiě)RenderContents方法
- protected override void RenderContents(HtmlTextWriter output) {
- output.Write("Text = ");
- output.Write(Text);
- output.Write("<br/>");
- output.Write("TextInViewState = ");
- output.Write(TextInViewState);
- }
- }
- }
如上代碼所示,控件實(shí)現(xiàn)了兩個(gè)屬性Text和TextInViewState。Text屬性使用了私有變量_text創(chuàng)建,這種實(shí)現(xiàn)無(wú)法保持該屬性的狀態(tài)信息。TextInViewState屬性使用了ViewState,其通過(guò)set訪問(wèn)器,將屬性值寫(xiě)入ViewState["TextInViewState"]對(duì)象中,通過(guò)get訪問(wèn)器,從對(duì)象ViewState["TextInViewState "]中獲取屬性值。這就是視圖狀態(tài)處理最簡(jiǎn)單的方法。當(dāng)使用ViewState作為屬性存儲(chǔ)時(shí),自定義服務(wù)器控件可以自行完成簡(jiǎn)單的狀態(tài)信息管理,例如,TrackViewState、SaveViewState、LoadViewState等。當(dāng)然,開(kāi)發(fā)人員也可以通過(guò)重寫(xiě)方法自定義狀態(tài)管理邏輯程序。在本例中,視圖狀態(tài)管理過(guò)程都是由.NET框架自動(dòng)完成的。
下面列舉了為測(cè)試以上自定義服務(wù)器控件而創(chuàng)建的Default.aspx文件源代碼。
- <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
- <%@ Register Namespace="WebControlLibrary" Assembly="WebControlLibrary" TagPrefix="sample" %>
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <script runat="server">
- void Button1_Click(object sender, EventArgs e) {
- demoLabel.Text = TextBox1.Text;
- demoLabel.TextInViewState = TextBox2.Text;
- }
- </script>
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head runat="server">
- <title>使用視圖狀態(tài)ViewState</title>
- </head>
- <body style="font-size: small;">
- <form id="form1" runat="server"> <div>
以上代碼顯示在頁(yè)面中包括兩個(gè)文本框,兩個(gè)按鈕,以及一個(gè)自定義服務(wù)器控件LabelInViewState。如事件處理程序Button1_Click所示,當(dāng)單擊"提交"按鈕時(shí),LabelInViewState控件將獲取文本框中文本,并顯示出來(lái)。應(yīng)用程序效果圖如圖1和圖2所示。
圖1 單擊提交按鈕
圖2 單擊重載按鈕
如圖1所示,當(dāng)用戶(hù)在兩個(gè)文本框中填寫(xiě)了文本,并單擊"提交"按鈕引發(fā)頁(yè)面回傳。此時(shí),填寫(xiě)的文本內(nèi)容將提交到服務(wù)器,并參與Button1_Click事件處理程序。這樣,LabelInViewState控件則顯示出了Text和TextInViewState屬性值。之后,當(dāng)用戶(hù)單擊"重載"按鈕時(shí),文本框內(nèi)容仍然提交到服務(wù)器,但是,由于沒(méi)有對(duì)應(yīng)的事件處理程序,因此,LabelInViewState控件只顯示已經(jīng)存在的狀態(tài)信息(即單擊提交按鈕之后保存的狀態(tài)),即Text屬性值為空,而TextInViewState屬性值為tom@tom.com。通過(guò)以上過(guò)程可知,TextInViewState屬性值都存儲(chǔ)在視圖狀態(tài)ViewState中,因此,在頁(yè)面往返過(guò)程中,該屬性值得以保持,而Text只簡(jiǎn)單使用了私有變量,所以狀態(tài)信息無(wú)法保持。另外,需要注意的是,由于默認(rèn)情況下,頁(yè)面啟用了視圖狀態(tài)EnableViewState = "true",才能實(shí)現(xiàn)以上效果。
小結(jié)
本文主要介紹了視圖狀態(tài)的基本概念,并通過(guò)一個(gè)典型示例說(shuō)明了應(yīng)用方法??赡懿糠肿x者已經(jīng)認(rèn)識(shí)到,如果禁用了頁(yè)面或者控件的視圖狀態(tài),即設(shè)置EnableViewState = "false",那么上文服務(wù)器控件的屬性TextViewState不是不能使用了嗎?這的確是視圖狀態(tài)的缺陷所在。然而,這并不是說(shuō)就無(wú)法解決這個(gè)問(wèn)題了。在下文中,筆者將介紹另外一種ASP.NET 2.0新增的,與視圖狀態(tài)極為類(lèi)似的技術(shù)特性--控件狀態(tài)--它就能夠很好的解決禁用視圖狀態(tài)的問(wèn)題。