西西軟件下載最安全的下載網(wǎng)站、值得信賴的軟件下載站!

首頁編程開發(fā)ASP.NET → ASP.NET編程中Web頁面幾種狀態(tài)管理總結(jié)

ASP.NET編程中Web頁面幾種狀態(tài)管理總結(jié)

相關(guān)文章發(fā)表評論 來源:西西整理時間:2012/11/21 8:45:07字體大�。�A-A+

作者:西西點擊:0次評論:0次標(biāo)簽: Cookie Session

  • 類型:站長工具大�。�5KB語言:中文 評分:7.5
  • 標(biāo)簽:
立即下載

由于HTTP協(xié)議的無狀態(tài)特性,導(dǎo)致在ASP.NET編程中,每個請求都會在服務(wù)端從頭到執(zhí)行一次管線過程, 對于ASP.NET頁面來說,Page對象都會重新創(chuàng)建,所有控件以及內(nèi)容都會重新生成, 因此,如果希望上一次的頁面狀態(tài)能夠在后續(xù)頁面中保留,則必需引入狀態(tài)管理功能。

ASP.NET為了實現(xiàn)狀態(tài)管理功能,提供了8種方法,可幫助我們在頁面之間或者整個用戶會話期間保留狀態(tài)數(shù)據(jù)。 這些方法分為二類:視圖狀態(tài)、控件狀態(tài)、隱藏域、Cookie 和查詢字符串會以不同方式將數(shù)據(jù)發(fā)送到客戶端上。 而應(yīng)用程序狀態(tài)、會話狀態(tài)和配置文件屬性(Profile)則會將數(shù)據(jù)存儲到服務(wù)端。 雖然每種方法都有不同的優(yōu)點和缺點,對于小的項目來說,可以選擇自己認為最容易使用的方法, 然而,對于有著較高要求的程序,尤其是對于性能與擴展性比較關(guān)注的程序來說, 選擇不同的方法最終導(dǎo)致的差別可能就非常大了。

在這篇博客中,我將談?wù)勛约簩SP.NET狀態(tài)管理方面的一些看法。
注意:本文的觀點可能并不合適開發(fā)小型項目,因為我關(guān)注的不是易用性。

hidden-input( 隱藏域

hidden-input 這個名字我是取的,表示所有type="hidden"的input標(biāo)簽元素。 在中文版的MSDN中,也稱之為 隱藏域 。 hidden-input通常存在于HTML表單之內(nèi),它不會顯示到頁面中, 但可以隨表單一起提交,因此,經(jīng)常用于維護當(dāng)前頁面的相關(guān)狀態(tài),在服務(wù)端我們可以使用Request.Form[]來訪問這些數(shù)據(jù)。

一般說來,我通常使用hidden-input來保存一些中間結(jié)果,用于在多次提交中維持一系列狀態(tài), 或者用它來保存一些固定參數(shù)用來提交給其它頁面(或網(wǎng)站)。 在這些場景中,我不希望用戶看到這些數(shù)據(jù),因此,使用hidden-input是比較方便的。

關(guān)于表單的更多介紹可參考我的博客:細說 Form (表單)

在ASP.NET WebForm框架中,我們可以使用HiddenField控件來創(chuàng)建一個hidden-input控件,并可以在服務(wù)端操作它, 還可以直接以手寫的方式使用隱藏域,例如:

<input type="hidden" name="hidden-1" value="aaaaaaa" />
<input type="hidden" name="hidden-2" value="bbbbbbb" />
<input type="hidden" name="hidden-3" value="ccccccc" />

另外,我們還可以調(diào)用ClientScript.RegisterHiddenField()方法來創(chuàng)建隱藏域:

ClientScript.RegisterHiddenField("hidden-4", "ddddddddd");

輸出結(jié)果:

<input type="hidden" name="hidden-4" id="hidden-4" value="ddddddddd" />

這三種方法對于生成的HTML代碼來說,主要差別在于它們出現(xiàn)位置不同:
1. HiddenField控件:由HiddenField的出現(xiàn)位置來決定(在form內(nèi)部)。
2. RegisterHiddenField方法:在form標(biāo)簽的開頭位置。
3. hidden-input:你寫在哪里就是哪里。

優(yōu)點:
1. 不需要任何服務(wù)器資源:隱藏域隨頁面一起發(fā)送到客戶端。
2. 廣泛的支持:幾乎所有瀏覽器和客戶端設(shè)備都支持具有隱藏域的表單。
3. 實現(xiàn)簡單:隱藏域是標(biāo)準(zhǔn)的 HTML 控件,不需要復(fù)雜的編程邏輯。

缺點:
1. 不能在多頁面跳轉(zhuǎn)之間維持狀態(tài)。
2. 用戶可見,保存敏感數(shù)據(jù)時需要加密。

QueryString

查詢字符串是存在于 URL 結(jié)尾的一段數(shù)據(jù)。下面是一個典型的查詢字符串示例(紅色部分文字):

http://www.abc.com/demo.aspx?k1=aaa&k2=bbb&k3=ccc

查詢字符串經(jīng)常用于頁面的數(shù)據(jù)過濾,例如:
1. 給列表頁面增加分頁參數(shù),list.aspx?page=2
2. 給列表頁面增加過慮范圍,Product.aspx?categoryId=5
3. 顯示特定記錄,ProductInfo.aspx?page=3

關(guān)于查詢字符串的用法,我補充二點:
1. 可以調(diào)用HttpUtility.ParseQueryString()來解析查詢字符串。
2. 允許參數(shù)名重復(fù):list.aspx?page=2&page=3,因此在修改URL參數(shù)時,使用替換方式而不是追加。
  關(guān)于參數(shù)重名的讀取問題,請參考我的博客:細說 Request[]與Request.Params[]

優(yōu)點:
1. 不需要任何服務(wù)器資源:查詢字符串的數(shù)據(jù)包含在每個URL中。
2. 廣泛的支持:幾乎所有的瀏覽器和客戶端設(shè)備均支持使用查詢字符串傳遞參數(shù)值。
3. 實現(xiàn)簡單:在服務(wù)端直接訪問Request.QueryString[]可讀取數(shù)據(jù)。
4. 頁面?zhèn)髦岛唵危?lt;a href="url">或者 Response.Redirect(url) 都可以實現(xiàn)。

缺點:
1. 有長度限制。
2. 用戶可見,不能保存敏感數(shù)據(jù)。

Cookie

由于HTTP協(xié)議是無狀態(tài)的,對于一個瀏覽器發(fā)出的多次請求,web服務(wù)器無法區(qū)分它們是不是來源于同一個瀏覽器。所以,需要額外的數(shù)據(jù)用于維護會話。 Cookie 正是這樣的一段隨HTTP請求一起被傳遞的額外數(shù)據(jù)。 Cookie 是一小段文本信息,它的工作方式就是伴隨著用戶請求和頁面在 Web 服務(wù)器和瀏覽器之間傳遞。Cookie 包含每次用戶訪問站點時 Web 應(yīng)用程序都可以讀取的信息。

與hidden-input, QueryString相比,Cookie有更多的屬性,許多瀏覽器可以直接查看這些信息:

由于Cookie擁有這些屬性,因此在客戶端狀態(tài)管理中可以實現(xiàn)更多的功能,尤其是在實現(xiàn)客戶端會話方面具有不可替代的作用。

關(guān)于Cookie的更多講解,請參考我的另一篇博客:細說Cookie

優(yōu)點:
1. 可配置到期規(guī)則:Cookie可以在客戶端長期存在,也可以在瀏覽器關(guān)閉時清除。
2. 不需要任何服務(wù)器資源:Cookie 存儲在客戶端。
3. 簡單性:Cookie 是一種基于文本的輕量結(jié)構(gòu),包含簡單的鍵值對。
4. 數(shù)據(jù)持久性:與其它的客戶端狀態(tài)數(shù)據(jù)相比,Cookie可以實現(xiàn)長久保存。 
5. 良好的擴展性:Cookie的讀寫要經(jīng)過ASP.NET管線,擁有無限的擴展性。

這里我要解釋一下Cookie 【良好的擴展性】是個什么概念,比如:
1. 我可以實現(xiàn)把Cookie保存到數(shù)據(jù)庫中而不需要修改現(xiàn)有的項目代碼。
2. 把SessionId這樣由ASP.NET產(chǎn)生的臨時Cookie讓它變成持久保存。

缺點:
1. 大小受到限制。
2. 增加請求頭長度。
3. 用戶可見,保存敏感數(shù)據(jù)時需要加密。

ApplicationState

應(yīng)用程序狀態(tài)是指采用HttpApplicationState實現(xiàn)的狀態(tài)維持方式,使用代碼如下:

Application.Lock();
Application["PageRequestCount"] = ((int)Application["PageRequestCount"]) + 1;
Application.UnLock();

對于這種方法,我不建議使用,因為:
1. 與使用靜態(tài)變量差不多,直接使用靜態(tài)變量可以不需要字典查找。
2. 選擇強類型的集合或者變量可以避免裝箱拆箱。

ViewState,ControlState

視圖狀態(tài),控件狀態(tài),二者是類似,在頁面中表現(xiàn)為一個hidden-input元素:

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="......................" />

控件狀態(tài)是ASP.NET 2.0中引入,與視圖狀態(tài)相比,它不允許關(guān)閉。
由于它們使用方式一致,而且視圖狀態(tài)是基于控件狀態(tài)的實現(xiàn)邏輯,所以我就不區(qū)分它們了。

在ASP.NET的早期,微軟為了能幫助廣大開發(fā)人員提高開發(fā)效率,引用入一大批的服務(wù)端控件,并為了能將事件編程機制引入ASP.NET中,又發(fā)明了ViewState。

這種方式雖然可以簡化開發(fā)工作量,然而卻有一些限制和缺點:
1. 視圖狀態(tài)的數(shù)據(jù)只能用于回發(fā)(postback)。
2. 視圖狀態(tài)的【濫用】容易導(dǎo)致生成的HTML較大,這會引起一個惡性循環(huán):
  a. 過大的ViewState在序列化過程中會消耗較多的服務(wù)器CPU資源,
  b. 過大的ViewState最終生成的HTML輸出也會很大,它會浪費服務(wù)端網(wǎng)絡(luò)資源,
  c. 過大的ViewState輸出導(dǎo)致表單在下次提交時,會占用客戶端網(wǎng)絡(luò)資源。
  d. 過大的ViewState數(shù)據(jù)上傳到服務(wù)端后,反序列化又會消耗較多的服務(wù)器CPU資源。
  因此,整個交互過程中,用戶一直在等待,用戶體驗極差。

在ASP.NET興起的年代,ViewState絕對是個了不起的發(fā)明。
然而,現(xiàn)在很多關(guān)于ASP.NET性能優(yōu)化的方法中,都會將【關(guān)閉ViewState】放在頭條位置。
為什么會這樣呢,大家可以自己思考一下了。

有些人認為:我現(xiàn)在做的程序只是在局域網(wǎng)內(nèi)使用,使用ViewState完全沒有問題!
然而,那些人或許沒有想過:
1. 未來用戶可能會把它部署在互聯(lián)網(wǎng)上運行(對于產(chǎn)品來說就是遇到大客戶了)。
2. 項目早期的設(shè)計與規(guī)劃,對后期的開發(fā)與維護來說,影響是巨大的,因為許多基礎(chǔ)部分通常是在早期開發(fā)的。
當(dāng)這二種情況的任何一種發(fā)生時,想再禁用ViewState,可能已經(jīng)晚了。

對于視圖狀態(tài),我認為它解決的問題比它引入的問題要多要復(fù)雜,
因此,我不想花時間整理它的優(yōu)缺點,我只想說一句:把它關(guān)了,在web.config中關(guān)了。

另外,我不排斥使用服務(wù)器控件,我認為:你可以使用服務(wù)端控件顯示數(shù)據(jù),但不要用它處理回發(fā)。

如果你仍然認為視圖狀態(tài)是不可缺少的,那我還是建議你看看ASP.NET MVC框架,看看沒有視圖狀態(tài)是不是照樣可以寫ASP.NET程序。

Session是ASP.NET實現(xiàn)的一種服務(wù)端會話技術(shù),它允許我們方便地在服務(wù)端保存與用戶有關(guān)的會話數(shù)據(jù)。

我認為Session只有一個優(yōu)點:最簡單的服務(wù)端會話實現(xiàn)方式。

缺點:
1. 當(dāng)mode="InProc"時,容易丟失數(shù)據(jù),為什么?因為網(wǎng)站會因為各種原因重啟。
2. 當(dāng)mode="InProc"時,Session保存的東西越多,就越占用服務(wù)器內(nèi)存,對于用戶在線人數(shù)較多的網(wǎng)站,服務(wù)器的內(nèi)存壓力會比較大。
3. 當(dāng)mode="InProc"時,程序的擴展性會受到影響,原因很簡單:服務(wù)器的內(nèi)存不能在多臺服務(wù)器間共享。
4. 當(dāng)采用進程外模式時,在每次請求中,不管你用不用會話數(shù)據(jù),所有的會話數(shù)據(jù)都為你準(zhǔn)備好了(反序列化),這其實很是浪費資源的。
5. 如果你沒有關(guān)閉Session,SessionStateModule就一直在工作中,尤其是全采用默認設(shè)置時,會對每個請求執(zhí)行一系列的調(diào)用,浪費資源。
6. 阻塞同一客戶端發(fā)起的多次請求(默認方式)。
7. 無Cookie會話可能會丟失數(shù)據(jù)(重新生成已過期的會話標(biāo)識符)。

Session的這些缺點也提醒我們:
1. 當(dāng)網(wǎng)站的在線人數(shù)較多時,一定不要用Session保存較大的對象。
2. 在密集型的AJAX型網(wǎng)站或者大量使用iframe的網(wǎng)站中,要關(guān)注Session可能引起的服務(wù)端阻塞問題。
3. 當(dāng)采用進程外模式時,不需要訪問Session的頁面,一定要關(guān)閉,否則會浪費服務(wù)器資源。

如果想了解更多的Session特點,以及我對Session的看法,可以瀏覽我的博客:Session,有沒有必要使用它?

Session的本質(zhì)有二點:
1. SessionId + 服務(wù)端字典:服務(wù)端字典保存了某個用戶的所有會話數(shù)據(jù)。
2. 用SessionId識別不同的客戶端:SessionId通常以Cookie形式發(fā)送到客戶端。

我認為了解Sesssion本質(zhì)非常有用,因為可以借鑒并實現(xiàn)自己的服務(wù)端會話方法。

關(guān)于Session我還想說一點:
有些新手喜歡用Session來實現(xiàn)身份認證功能,這是一種【不正確】的方法。
如果你的ASP.NET應(yīng)用程序需要身份認證功能,請使用 Forms身份認證 或者 Windows身份認證

Profile 在中文版的MSDN中被稱為 配置文件屬性,這個功能是在 ASP.NET 2.0 中引入的。

ASP.NET提供這個功能主要是為了簡化與用戶相關(guān)的個性化信息的讀寫方式。
簡化主要體現(xiàn)在3個方面:
1. 自動與某個用戶關(guān)聯(lián),已登錄用戶或者未登錄都支持。
2. 不需要我們設(shè)計用戶的個性化信息的保存表結(jié)構(gòu),只要修改配置文件就夠了。
3. 不需要我們實現(xiàn)數(shù)據(jù)的加載與保存邏輯,ASP.NET框架替我們實現(xiàn)好了。

為了使用Profile,我們首先在web.config中定義所需要的用戶個性化信息:

<profile>
    <properties>
        <add name="Address"/>
        <add name="Tel"/>
    </properties>
</profile>

然后,就可以在頁面中使用了:

為什么會這樣呢?
原因是ASP.NET已經(jīng)根據(jù)web.config為我們創(chuàng)建了一個新類型:

using System;
using System.Web.Profile;

public class ProfileCommon : ProfileBase
{
    public ProfileCommon();

    public virtual string Address { get; set; }
    public virtual string Tel { get; set; }

    public virtual ProfileCommon GetProfile(string username);
}

有了這個類型后,當(dāng)我們訪問HttpContext.Profile屬性時,ASP.NET會創(chuàng)建一個ProfileCommon的實例。 也正是由于Profile的強類型機制,在使用Profile時才會有智能提示功能。

如果我們希望為未登錄的匿名用戶也提供這種支持,需要將配置修改成:

<profile>
    <properties>
        <add name="Address" allowAnonymous="true" />
        <add name="Tel" allowAnonymous="true"/>
    </properties>
</profile>
<anonymousIdentification enabled="true" />

Profile中的每個屬性還允許指定類型和默認值,以及序列化方式,因此,擴展性還是比較好的。

盡管Profile看上去很美,然而,使用Profile的人卻很少。
比如我就不用它,我也沒見有人有過它。
為什么會這樣?

我個人認為:它與MemberShip一樣,是個雞肋。
通常說來,我們會為用戶信息創(chuàng)建一張User表,增加用戶信息時,會通過增加字段的方式解決。
我認為這樣集中的數(shù)據(jù)才會更好,而不是說,有一部分數(shù)據(jù)由我維護,另一部分數(shù)據(jù)由ASP.NET維護。

另一個特例是:我們根本不創(chuàng)建User表,直接使用MemberShip,那么Profile用來保存MemberShip沒有信息是有必要的。

還是給Profile做個總結(jié)吧:
優(yōu)點:使用簡單。
缺點:不實用。

各種狀態(tài)管理的對比與總結(jié)

前面分別介紹了ASP.NET的8種狀態(tài)管理技術(shù),這里打算給它們做個總結(jié)。

 客戶端服務(wù)端
數(shù)據(jù)安全性
數(shù)據(jù)長度限制受硬件限制
占用服務(wù)器資源
集群擴展性

表格中主要考察了數(shù)據(jù)保存與服務(wù)端水平擴展的相關(guān)重要指標(biāo)。

下面我來解釋表格的結(jié)果。
1. 客戶端方式的狀態(tài)數(shù)據(jù)(hidden-input, QueryString, Cookie):
  a. 數(shù)據(jù)對用戶來說,可見可修改,因此數(shù)據(jù)不安全。
  b. QueryString, Cookie 都有長度限制。
  c. 數(shù)據(jù)在客戶端,因此不占用服務(wù)端資源。這個特性對于在線人數(shù)很多的網(wǎng)站非常重要。
  d. 數(shù)據(jù)在客戶端,因此和服務(wù)端沒有耦合關(guān)系,WEB服務(wù)器可以更容易實現(xiàn)水平擴展。

2. 服務(wù)端方式的狀態(tài)數(shù)據(jù)(ApplicationState,ViewState,ControlState,Session,Profile):
  a. 數(shù)據(jù)對用戶不可見,因此安全性好。(ApplicationState,Session,Profile)
  b. 數(shù)所長度只受硬件限制,因此,對于在線人數(shù)較多的網(wǎng)站,需謹慎選擇。
  c. 對于存放在內(nèi)存中的狀態(tài)數(shù)據(jù),由于不能共享內(nèi)存,因此會限制水平擴展能力。
  d. 如果狀態(tài)數(shù)據(jù)保存到一臺機器,會有單點失敗的可能,也會限制了水平擴展能力。

從這個表格我們還可以得到以下結(jié)論:
1. 如果很關(guān)注數(shù)據(jù)的安全性,應(yīng)該首選服務(wù)端的狀態(tài)管理方法。
2. 如果你關(guān)注服務(wù)端的水平擴展性,應(yīng)該首選客戶端的狀態(tài)管理方法。

會話狀態(tài)的選擇

接下來,我們再來看看會話狀態(tài),它與狀態(tài)管理有著一些關(guān)系,屬于比較類似的概念。

談到會話狀態(tài),首先我要申明一點:會話狀態(tài)與狀態(tài)不是一回事。

本文前面所說的狀態(tài)分為二種:
1. 頁面之間的狀態(tài)。
2. 應(yīng)用程序范圍內(nèi)的狀態(tài)。

而會話狀態(tài)是針對某個用戶來說,他(她)在多次操作之間的狀態(tài)。
在用戶的操作期間,有可能狀態(tài)需要在頁面之間持續(xù)使用,
也有可能服務(wù)端程序做過重啟,但數(shù)據(jù)仍然有效。
因此,這種狀態(tài)數(shù)據(jù)更持久。

在ASP.NET中,使用會話狀態(tài)有二個選擇:Session 或者 Cookie 。
前者由ASP.NET實現(xiàn),并有可能依賴后者。
后者則由瀏覽器實現(xiàn),ASP.NET提供讀寫方法。

那么到底選擇哪個呢?
如果你要問我這個問題,我肯定會說:我選 Cookie !

下面是我選擇Cookie實現(xiàn)會話狀態(tài)的理由:
1. 不會有服務(wù)端阻塞問題。
2. 不占用服務(wù)端資源。
3. 水平擴展沒有限制。
4. 也支持過期設(shè)置,而且更靈活。
5. 可以在客戶端直接使用會話數(shù)據(jù)。
6. 可以實現(xiàn)更靈活的會話數(shù)據(jù)加載策略。
7. 擴展性較好(源于ASP.NET管線的擴展性)

如果選擇使用Cookie實現(xiàn)會話狀態(tài),有3點需要特別注意:
1. 不建議保存敏感數(shù)據(jù),除非已加密。
2. 只適合保存短小簡單的數(shù)據(jù)。
3. 如果會話數(shù)據(jù)較大,可以在客戶端保存用戶標(biāo)識,由服務(wù)端實現(xiàn)數(shù)據(jù)的加載保存邏輯。

或許有些人認為:每種技術(shù)都有它們的優(yōu)缺點,有各自的適用領(lǐng)域。
我表示贊同這句話。
但是,我們要清楚一點:每個項目的規(guī)模不一樣,性能以及擴展性要求也不同。
對于一個小的項目來說,選擇什么方法都不是問題,
但是,對于規(guī)模較大的項目,我們一定需要取舍。
取舍的目標(biāo)是:包裝越少越好,因為人家做了過多的包裝,就會有較多的限制,
所以,不要只關(guān)注現(xiàn)在的調(diào)用是否方便,其實只要你愿意包裝,你也可以讓復(fù)雜的調(diào)用簡單化。

改變開發(fā)方式,發(fā)現(xiàn)新方法

回想一下:為什么在ASP.NET中需要狀態(tài)管理?
答:因為與HTTP協(xié)議有關(guān),服務(wù)端沒有保存每個請求的上次頁面狀態(tài)。

為什么Windows計算器(這類)程序不用考慮會話問題呢?
答:因為這類程序的界面不需要重新生成,任何變量都可表示狀態(tài)。

再來看這樣一個場景:

圖片左邊是一個列表頁面,允許調(diào)整每條記錄的優(yōu)先級,但是有2個要求:
1. 在移動每條記錄時,必須輸入一個調(diào)整理由。
2. 只要輸入理由后,那條記錄可以任意調(diào)整多次。

顯然,完成這個任務(wù)必須要有狀態(tài)才能實現(xiàn)。

面對這個問題,你可以思考一下:選擇哪種ASP.NET支持的狀態(tài)管理方法都很麻煩。

怎么辦?

我的解決方法:創(chuàng)建一個JavaScript數(shù)組,用每個數(shù)組元素保存每條記錄的狀態(tài),
所有用戶交互操作用AJAX方式實現(xiàn),這樣頁面不會刷新,JavaScript變量中的狀態(tài)一直有效。
因此,很容易就能解決這個問題。

這個案例也提醒我們:當(dāng)發(fā)現(xiàn)ASP.NET提供的狀態(tài)管理功能全部不合適時, 我們需要改變開發(fā)方式了。

為什么WEB編程都有【無狀態(tài)】問題,而桌面程序沒有?
我認為與HTTP協(xié)議有關(guān),但沒有絕對的關(guān)系。
只要你能保證頁面不刷新,也能像桌面程序那樣,用JavaScript變量就能維護頁面狀態(tài)。

    相關(guān)評論

    閱讀本文后您有什么感想? 已有人給出評價!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過難過
    • 5 囧
    • 3 圍觀圍觀
    • 2 無聊無聊

    熱門評論

    最新評論

    發(fā)表評論 查看所有評論(0)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字數(shù): 0/500 (您的評論需要經(jīng)過審核才能顯示)
    推薦文章

    沒有數(shù)據(jù)

    最新文章
      沒有數(shù)據(jù)