由于HTTP協(xié)議的無(wú)狀態(tài)特性,導(dǎo)致在ASP.NET編程中,每個(gè)請(qǐng)求都會(huì)在服務(wù)端從頭到執(zhí)行一次管線(xiàn)過(guò)程, 對(duì)于ASP.NET頁(yè)面來(lái)說(shuō),Page對(duì)象都會(huì)重新創(chuàng)建,所有控件以及內(nèi)容都會(huì)重新生成, 因此,如果希望上一次的頁(yè)面狀態(tài)能夠在后續(xù)頁(yè)面中保留,則必需引入狀態(tài)管理功能。
ASP.NET為了實(shí)現(xiàn)狀態(tài)管理功能,提供了8種方法,可幫助我們?cè)陧?yè)面之間或者整個(gè)用戶(hù)會(huì)話(huà)期間保留狀態(tài)數(shù)據(jù)。 這些方法分為二類(lèi):視圖狀態(tài)、控件狀態(tài)、隱藏域、Cookie 和查詢(xún)字符串會(huì)以不同方式將數(shù)據(jù)發(fā)送到客戶(hù)端上。 而應(yīng)用程序狀態(tài)、會(huì)話(huà)狀態(tài)和配置文件屬性(Profile)則會(huì)將數(shù)據(jù)存儲(chǔ)到服務(wù)端。 雖然每種方法都有不同的優(yōu)點(diǎn)和缺點(diǎn),對(duì)于小的項(xiàng)目來(lái)說(shuō),可以選擇自己認(rèn)為最容易使用的方法, 然而,對(duì)于有著較高要求的程序,尤其是對(duì)于性能與擴(kuò)展性比較關(guān)注的程序來(lái)說(shuō), 選擇不同的方法最終導(dǎo)致的差別可能就非常大了。
在這篇博客中,我將談?wù)勛约簩?duì)ASP.NET狀態(tài)管理方面的一些看法。
注意:本文的觀點(diǎn)可能并不合適開(kāi)發(fā)小型項(xiàng)目,因?yàn)槲谊P(guān)注的不是易用性。
hidden-input( 隱藏域)
hidden-input 這個(gè)名字我是取的,表示所有type="hidden"的input標(biāo)簽元素。 在中文版的MSDN中,也稱(chēng)之為 隱藏域 。 hidden-input通常存在于HTML表單之內(nèi),它不會(huì)顯示到頁(yè)面中, 但可以隨表單一起提交,因此,經(jīng)常用于維護(hù)當(dāng)前頁(yè)面的相關(guān)狀態(tài),在服務(wù)端我們可以使用Request.Form[]來(lái)訪問(wèn)這些數(shù)據(jù)。
一般說(shuō)來(lái),我通常使用hidden-input來(lái)保存一些中間結(jié)果,用于在多次提交中維持一系列狀態(tài), 或者用它來(lái)保存一些固定參數(shù)用來(lái)提交給其它頁(yè)面(或網(wǎng)站)。 在這些場(chǎng)景中,我不希望用戶(hù)看到這些數(shù)據(jù),因此,使用hidden-input是比較方便的。
關(guān)于表單的更多介紹可參考我的博客:細(xì)說(shuō) Form (表單)
在ASP.NET WebForm框架中,我們可以使用HiddenField控件來(lái)創(chuàng)建一個(gè)hidden-input控件,并可以在服務(wù)端操作它, 還可以直接以手寫(xiě)的方式使用隱藏域,例如:
<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()方法來(lái)創(chuàng)建隱藏域:
ClientScript.RegisterHiddenField("hidden-4", "ddddddddd");
輸出結(jié)果:
<input type="hidden" name="hidden-4" id="hidden-4" value="ddddddddd" />
這三種方法對(duì)于生成的HTML代碼來(lái)說(shuō),主要差別在于它們出現(xiàn)位置不同:
1. HiddenField控件:由HiddenField的出現(xiàn)位置來(lái)決定(在form內(nèi)部)。
2. RegisterHiddenField方法:在form標(biāo)簽的開(kāi)頭位置。
3. hidden-input:你寫(xiě)在哪里就是哪里。
優(yōu)點(diǎn):
1. 不需要任何服務(wù)器資源:隱藏域隨頁(yè)面一起發(fā)送到客戶(hù)端。
2. 廣泛的支持:幾乎所有瀏覽器和客戶(hù)端設(shè)備都支持具有隱藏域的表單。
3. 實(shí)現(xiàn)簡(jiǎn)單:隱藏域是標(biāo)準(zhǔn)的 HTML 控件,不需要復(fù)雜的編程邏輯。
缺點(diǎn):
1. 不能在多頁(yè)面跳轉(zhuǎn)之間維持狀態(tài)。
2. 用戶(hù)可見(jiàn),保存敏感數(shù)據(jù)時(shí)需要加密。
QueryString
查詢(xún)字符串是存在于 URL 結(jié)尾的一段數(shù)據(jù)。下面是一個(gè)典型的查詢(xún)字符串示例(紅色部分文字):
http://www.abc.com/demo.aspx?k1=aaa&k2=bbb&k3=ccc
查詢(xún)字符串經(jīng)常用于頁(yè)面的數(shù)據(jù)過(guò)濾,例如:
1. 給列表頁(yè)面增加分頁(yè)參數(shù),list.aspx?page=2
2. 給列表頁(yè)面增加過(guò)慮范圍,Product.aspx?categoryId=5
3. 顯示特定記錄,ProductInfo.aspx?page=3
關(guān)于查詢(xún)字符串的用法,我補(bǔ)充二點(diǎn):
1. 可以調(diào)用HttpUtility.ParseQueryString()來(lái)解析查詢(xún)字符串。
2. 允許參數(shù)名重復(fù):list.aspx?page=2&page=3,因此在修改URL參數(shù)時(shí),使用替換方式而不是追加。
關(guān)于參數(shù)重名的讀取問(wèn)題,請(qǐng)參考我的博客:細(xì)說(shuō) Request[]與Request.Params[]
優(yōu)點(diǎn):
1. 不需要任何服務(wù)器資源:查詢(xún)字符串的數(shù)據(jù)包含在每個(gè)URL中。
2. 廣泛的支持:幾乎所有的瀏覽器和客戶(hù)端設(shè)備均支持使用查詢(xún)字符串傳遞參數(shù)值。
3. 實(shí)現(xiàn)簡(jiǎn)單:在服務(wù)端直接訪問(wèn)Request.QueryString[]可讀取數(shù)據(jù)。
4. 頁(yè)面?zhèn)髦岛?jiǎn)單:<a href="url">或者 Response.Redirect(url) 都可以實(shí)現(xiàn)。
缺點(diǎn):
1. 有長(zhǎng)度限制。
2. 用戶(hù)可見(jiàn),不能保存敏感數(shù)據(jù)。
Cookie
由于HTTP協(xié)議是無(wú)狀態(tài)的,對(duì)于一個(gè)瀏覽器發(fā)出的多次請(qǐng)求,web服務(wù)器無(wú)法區(qū)分它們是不是來(lái)源于同一個(gè)瀏覽器。所以,需要額外的數(shù)據(jù)用于維護(hù)會(huì)話(huà)。 Cookie 正是這樣的一段隨HTTP請(qǐng)求一起被傳遞的額外數(shù)據(jù)。 Cookie 是一小段文本信息,它的工作方式就是伴隨著用戶(hù)請(qǐng)求和頁(yè)面在 Web 服務(wù)器和瀏覽器之間傳遞。Cookie 包含每次用戶(hù)訪問(wèn)站點(diǎn)時(shí) Web 應(yīng)用程序都可以讀取的信息。
與hidden-input, QueryString相比,Cookie有更多的屬性,許多瀏覽器可以直接查看這些信息:
由于Cookie擁有這些屬性,因此在客戶(hù)端狀態(tài)管理中可以實(shí)現(xiàn)更多的功能,尤其是在實(shí)現(xiàn)客戶(hù)端會(huì)話(huà)方面具有不可替代的作用。
關(guān)于Cookie的更多講解,請(qǐng)參考我的另一篇博客:細(xì)說(shuō)Cookie
優(yōu)點(diǎn):
1. 可配置到期規(guī)則:Cookie可以在客戶(hù)端長(zhǎng)期存在,也可以在瀏覽器關(guān)閉時(shí)清除。
2. 不需要任何服務(wù)器資源:Cookie 存儲(chǔ)在客戶(hù)端。
3. 簡(jiǎn)單性:Cookie 是一種基于文本的輕量結(jié)構(gòu),包含簡(jiǎn)單的鍵值對(duì)。
4. 數(shù)據(jù)持久性:與其它的客戶(hù)端狀態(tài)數(shù)據(jù)相比,Cookie可以實(shí)現(xiàn)長(zhǎng)久保存。
5. 良好的擴(kuò)展性:Cookie的讀寫(xiě)要經(jīng)過(guò)ASP.NET管線(xiàn),擁有無(wú)限的擴(kuò)展性。
這里我要解釋一下Cookie 【良好的擴(kuò)展性】是個(gè)什么概念,比如:
1. 我可以實(shí)現(xiàn)把Cookie保存到數(shù)據(jù)庫(kù)中而不需要修改現(xiàn)有的項(xiàng)目代碼。
2. 把SessionId這樣由ASP.NET產(chǎn)生的臨時(shí)Cookie讓它變成持久保存。
缺點(diǎn):
1. 大小受到限制。
2. 增加請(qǐng)求頭長(zhǎng)度。
3. 用戶(hù)可見(jiàn),保存敏感數(shù)據(jù)時(shí)需要加密。
ApplicationState
應(yīng)用程序狀態(tài)是指采用HttpApplicationState實(shí)現(xiàn)的狀態(tài)維持方式,使用代碼如下:
Application.Lock(); Application["PageRequestCount"] = ((int)Application["PageRequestCount"]) + 1; Application.UnLock();
對(duì)于這種方法,我不建議使用,因?yàn)椋?br />1. 與使用靜態(tài)變量差不多,直接使用靜態(tài)變量可以不需要字典查找。
2. 選擇強(qiáng)類(lèi)型的集合或者變量可以避免裝箱拆箱。
ViewState,ControlState
視圖狀態(tài),控件狀態(tài),二者是類(lèi)似,在頁(yè)面中表現(xiàn)為一個(gè)hidden-input元素:
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="......................" />
控件狀態(tài)是ASP.NET 2.0中引入,與視圖狀態(tài)相比,它不允許關(guān)閉。
由于它們使用方式一致,而且視圖狀態(tài)是基于控件狀態(tài)的實(shí)現(xiàn)邏輯,所以我就不區(qū)分它們了。
在ASP.NET的早期,微軟為了能幫助廣大開(kāi)發(fā)人員提高開(kāi)發(fā)效率,引用入一大批的服務(wù)端控件,并為了能將事件編程機(jī)制引入ASP.NET中,又發(fā)明了ViewState。
這種方式雖然可以簡(jiǎn)化開(kāi)發(fā)工作量,然而卻有一些限制和缺點(diǎn):
1. 視圖狀態(tài)的數(shù)據(jù)只能用于回發(fā)(postback)。
2. 視圖狀態(tài)的【濫用】容易導(dǎo)致生成的HTML較大,這會(huì)引起一個(gè)惡性循環(huán):
a. 過(guò)大的ViewState在序列化過(guò)程中會(huì)消耗較多的服務(wù)器CPU資源,
b. 過(guò)大的ViewState最終生成的HTML輸出也會(huì)很大,它會(huì)浪費(fèi)服務(wù)端網(wǎng)絡(luò)資源,
c. 過(guò)大的ViewState輸出導(dǎo)致表單在下次提交時(shí),會(huì)占用客戶(hù)端網(wǎng)絡(luò)資源。
d. 過(guò)大的ViewState數(shù)據(jù)上傳到服務(wù)端后,反序列化又會(huì)消耗較多的服務(wù)器CPU資源。
因此,整個(gè)交互過(guò)程中,用戶(hù)一直在等待,用戶(hù)體驗(yàn)極差。
在ASP.NET興起的年代,ViewState絕對(duì)是個(gè)了不起的發(fā)明。
然而,現(xiàn)在很多關(guān)于ASP.NET性能優(yōu)化的方法中,都會(huì)將【關(guān)閉ViewState】放在頭條位置。
為什么會(huì)這樣呢,大家可以自己思考一下了。
有些人認(rèn)為:我現(xiàn)在做的程序只是在局域網(wǎng)內(nèi)使用,使用ViewState完全沒(méi)有問(wèn)題!
然而,那些人或許沒(méi)有想過(guò):
1. 未來(lái)用戶(hù)可能會(huì)把它部署在互聯(lián)網(wǎng)上運(yùn)行(對(duì)于產(chǎn)品來(lái)說(shuō)就是遇到大客戶(hù)了)。
2. 項(xiàng)目早期的設(shè)計(jì)與規(guī)劃,對(duì)后期的開(kāi)發(fā)與維護(hù)來(lái)說(shuō),影響是巨大的,因?yàn)樵S多基礎(chǔ)部分通常是在早期開(kāi)發(fā)的。
當(dāng)這二種情況的任何一種發(fā)生時(shí),想再禁用ViewState,可能已經(jīng)晚了。
對(duì)于視圖狀態(tài),我認(rèn)為它解決的問(wèn)題比它引入的問(wèn)題要多要復(fù)雜,
因此,我不想花時(shí)間整理它的優(yōu)缺點(diǎn),我只想說(shuō)一句:把它關(guān)了,在web.config中關(guān)了。
另外,我不排斥使用服務(wù)器控件,我認(rèn)為:你可以使用服務(wù)端控件顯示數(shù)據(jù),但不要用它處理回發(fā)。
如果你仍然認(rèn)為視圖狀態(tài)是不可缺少的,那我還是建議你看看ASP.NET MVC框架,看看沒(méi)有視圖狀態(tài)是不是照樣可以寫(xiě)ASP.NET程序。
Session是ASP.NET實(shí)現(xiàn)的一種服務(wù)端會(huì)話(huà)技術(shù),它允許我們方便地在服務(wù)端保存與用戶(hù)有關(guān)的會(huì)話(huà)數(shù)據(jù)。
我認(rèn)為Session只有一個(gè)優(yōu)點(diǎn):最簡(jiǎn)單的服務(wù)端會(huì)話(huà)實(shí)現(xiàn)方式。
缺點(diǎn):
1. 當(dāng)mode="InProc"時(shí),容易丟失數(shù)據(jù),為什么?因?yàn)榫W(wǎng)站會(huì)因?yàn)楦鞣N原因重啟。
2. 當(dāng)mode="InProc"時(shí),Session保存的東西越多,就越占用服務(wù)器內(nèi)存,對(duì)于用戶(hù)在線(xiàn)人數(shù)較多的網(wǎng)站,服務(wù)器的內(nèi)存壓力會(huì)比較大。
3. 當(dāng)mode="InProc"時(shí),程序的擴(kuò)展性會(huì)受到影響,原因很簡(jiǎn)單:服務(wù)器的內(nèi)存不能在多臺(tái)服務(wù)器間共享。
4. 當(dāng)采用進(jìn)程外模式時(shí),在每次請(qǐng)求中,不管你用不用會(huì)話(huà)數(shù)據(jù),所有的會(huì)話(huà)數(shù)據(jù)都為你準(zhǔn)備好了(反序列化),這其實(shí)很是浪費(fèi)資源的。
5. 如果你沒(méi)有關(guān)閉Session,SessionStateModule就一直在工作中,尤其是全采用默認(rèn)設(shè)置時(shí),會(huì)對(duì)每個(gè)請(qǐng)求執(zhí)行一系列的調(diào)用,浪費(fèi)資源。
6. 阻塞同一客戶(hù)端發(fā)起的多次請(qǐng)求(默認(rèn)方式)。
7. 無(wú)Cookie會(huì)話(huà)可能會(huì)丟失數(shù)據(jù)(重新生成已過(guò)期的會(huì)話(huà)標(biāo)識(shí)符)。
Session的這些缺點(diǎn)也提醒我們:
1. 當(dāng)網(wǎng)站的在線(xiàn)人數(shù)較多時(shí),一定不要用Session保存較大的對(duì)象。
2. 在密集型的AJAX型網(wǎng)站或者大量使用iframe的網(wǎng)站中,要關(guān)注Session可能引起的服務(wù)端阻塞問(wèn)題。
3. 當(dāng)采用進(jìn)程外模式時(shí),不需要訪問(wèn)Session的頁(yè)面,一定要關(guān)閉,否則會(huì)浪費(fèi)服務(wù)器資源。
如果想了解更多的Session特點(diǎn),以及我對(duì)Session的看法,可以瀏覽我的博客:Session,有沒(méi)有必要使用它?
Session的本質(zhì)有二點(diǎn):
1. SessionId + 服務(wù)端字典:服務(wù)端字典保存了某個(gè)用戶(hù)的所有會(huì)話(huà)數(shù)據(jù)。
2. 用SessionId識(shí)別不同的客戶(hù)端:SessionId通常以Cookie形式發(fā)送到客戶(hù)端。
我認(rèn)為了解Sesssion本質(zhì)非常有用,因?yàn)榭梢越梃b并實(shí)現(xiàn)自己的服務(wù)端會(huì)話(huà)方法。
關(guān)于Session我還想說(shuō)一點(diǎn):
有些新手喜歡用Session來(lái)實(shí)現(xiàn)身份認(rèn)證功能,這是一種【不正確】的方法。
如果你的ASP.NET應(yīng)用程序需要身份認(rèn)證功能,請(qǐng)使用 Forms身份認(rèn)證 或者 Windows身份認(rèn)證
Profile 在中文版的MSDN中被稱(chēng)為 配置文件屬性,這個(gè)功能是在 ASP.NET 2.0 中引入的。
ASP.NET提供這個(gè)功能主要是為了簡(jiǎn)化與用戶(hù)相關(guān)的個(gè)性化信息的讀寫(xiě)方式。
簡(jiǎn)化主要體現(xiàn)在3個(gè)方面:
1. 自動(dòng)與某個(gè)用戶(hù)關(guān)聯(lián),已登錄用戶(hù)或者未登錄都支持。
2. 不需要我們?cè)O(shè)計(jì)用戶(hù)的個(gè)性化信息的保存表結(jié)構(gòu),只要修改配置文件就夠了。
3. 不需要我們實(shí)現(xiàn)數(shù)據(jù)的加載與保存邏輯,ASP.NET框架替我們實(shí)現(xiàn)好了。
為了使用Profile,我們首先在web.config中定義所需要的用戶(hù)個(gè)性化信息:
<profile> <properties> <add name="Address"/> <add name="Tel"/> </properties> </profile>
然后,就可以在頁(yè)面中使用了:
為什么會(huì)這樣呢?
原因是ASP.NET已經(jīng)根據(jù)web.config為我們創(chuàng)建了一個(gè)新類(lèi)型:
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); }
有了這個(gè)類(lèi)型后,當(dāng)我們?cè)L問(wèn)HttpContext.Profile屬性時(shí),ASP.NET會(huì)創(chuàng)建一個(gè)ProfileCommon的實(shí)例。 也正是由于Profile的強(qiáng)類(lèi)型機(jī)制,在使用Profile時(shí)才會(huì)有智能提示功能。
如果我們希望為未登錄的匿名用戶(hù)也提供這種支持,需要將配置修改成:
<profile> <properties> <add name="Address" allowAnonymous="true" /> <add name="Tel" allowAnonymous="true"/> </properties> </profile> <anonymousIdentification enabled="true" />
Profile中的每個(gè)屬性還允許指定類(lèi)型和默認(rèn)值,以及序列化方式,因此,擴(kuò)展性還是比較好的。
盡管Profile看上去很美,然而,使用Profile的人卻很少。
比如我就不用它,我也沒(méi)見(jiàn)有人有過(guò)它。
為什么會(huì)這樣?
我個(gè)人認(rèn)為:它與MemberShip一樣,是個(gè)雞肋。
通常說(shuō)來(lái),我們會(huì)為用戶(hù)信息創(chuàng)建一張User表,增加用戶(hù)信息時(shí),會(huì)通過(guò)增加字段的方式解決。
我認(rèn)為這樣集中的數(shù)據(jù)才會(huì)更好,而不是說(shuō),有一部分?jǐn)?shù)據(jù)由我維護(hù),另一部分?jǐn)?shù)據(jù)由ASP.NET維護(hù)。
另一個(gè)特例是:我們根本不創(chuàng)建User表,直接使用MemberShip,那么Profile用來(lái)保存MemberShip沒(méi)有信息是有必要的。
還是給Profile做個(gè)總結(jié)吧:
優(yōu)點(diǎn):使用簡(jiǎn)單。
缺點(diǎn):不實(shí)用。
各種狀態(tài)管理的對(duì)比與總結(jié)
前面分別介紹了ASP.NET的8種狀態(tài)管理技術(shù),這里打算給它們做個(gè)總結(jié)。
客戶(hù)端 | 服務(wù)端 | |
數(shù)據(jù)安全性 | 差 | 好 |
數(shù)據(jù)長(zhǎng)度限制 | 有 | 受硬件限制 |
占用服務(wù)器資源 | 否 | 是 |
集群擴(kuò)展性 | 好 | 差 |
表格中主要考察了數(shù)據(jù)保存與服務(wù)端水平擴(kuò)展的相關(guān)重要指標(biāo)。
下面我來(lái)解釋表格的結(jié)果。
1. 客戶(hù)端方式的狀態(tài)數(shù)據(jù)(hidden-input, QueryString, Cookie):
a. 數(shù)據(jù)對(duì)用戶(hù)來(lái)說(shuō),可見(jiàn)可修改,因此數(shù)據(jù)不安全。
b. QueryString, Cookie 都有長(zhǎng)度限制。
c. 數(shù)據(jù)在客戶(hù)端,因此不占用服務(wù)端資源。這個(gè)特性對(duì)于在線(xiàn)人數(shù)很多的網(wǎng)站非常重要。
d. 數(shù)據(jù)在客戶(hù)端,因此和服務(wù)端沒(méi)有耦合關(guān)系,WEB服務(wù)器可以更容易實(shí)現(xiàn)水平擴(kuò)展。
2. 服務(wù)端方式的狀態(tài)數(shù)據(jù)(ApplicationState,ViewState,ControlState,Session,Profile):
a. 數(shù)據(jù)對(duì)用戶(hù)不可見(jiàn),因此安全性好。(ApplicationState,Session,Profile)
b. 數(shù)所長(zhǎng)度只受硬件限制,因此,對(duì)于在線(xiàn)人數(shù)較多的網(wǎng)站,需謹(jǐn)慎選擇。
c. 對(duì)于存放在內(nèi)存中的狀態(tài)數(shù)據(jù),由于不能共享內(nèi)存,因此會(huì)限制水平擴(kuò)展能力。
d. 如果狀態(tài)數(shù)據(jù)保存到一臺(tái)機(jī)器,會(huì)有單點(diǎn)失敗的可能,也會(huì)限制了水平擴(kuò)展能力。
從這個(gè)表格我們還可以得到以下結(jié)論:
1. 如果很關(guān)注數(shù)據(jù)的安全性,應(yīng)該首選服務(wù)端的狀態(tài)管理方法。
2. 如果你關(guān)注服務(wù)端的水平擴(kuò)展性,應(yīng)該首選客戶(hù)端的狀態(tài)管理方法。
會(huì)話(huà)狀態(tài)的選擇
接下來(lái),我們?cè)賮?lái)看看會(huì)話(huà)狀態(tài),它與狀態(tài)管理有著一些關(guān)系,屬于比較類(lèi)似的概念。
談到會(huì)話(huà)狀態(tài),首先我要申明一點(diǎn):會(huì)話(huà)狀態(tài)與狀態(tài)不是一回事。
本文前面所說(shuō)的狀態(tài)分為二種:
1. 頁(yè)面之間的狀態(tài)。
2. 應(yīng)用程序范圍內(nèi)的狀態(tài)。
而會(huì)話(huà)狀態(tài)是針對(duì)某個(gè)用戶(hù)來(lái)說(shuō),他(她)在多次操作之間的狀態(tài)。
在用戶(hù)的操作期間,有可能狀態(tài)需要在頁(yè)面之間持續(xù)使用,
也有可能服務(wù)端程序做過(guò)重啟,但數(shù)據(jù)仍然有效。
因此,這種狀態(tài)數(shù)據(jù)更持久。
在ASP.NET中,使用會(huì)話(huà)狀態(tài)有二個(gè)選擇:Session 或者 Cookie 。
前者由ASP.NET實(shí)現(xiàn),并有可能依賴(lài)后者。
后者則由瀏覽器實(shí)現(xiàn),ASP.NET提供讀寫(xiě)方法。
那么到底選擇哪個(gè)呢?
如果你要問(wèn)我這個(gè)問(wèn)題,我肯定會(huì)說(shuō):我選 Cookie !
下面是我選擇Cookie實(shí)現(xiàn)會(huì)話(huà)狀態(tài)的理由:
1. 不會(huì)有服務(wù)端阻塞問(wèn)題。
2. 不占用服務(wù)端資源。
3. 水平擴(kuò)展沒(méi)有限制。
4. 也支持過(guò)期設(shè)置,而且更靈活。
5. 可以在客戶(hù)端直接使用會(huì)話(huà)數(shù)據(jù)。
6. 可以實(shí)現(xiàn)更靈活的會(huì)話(huà)數(shù)據(jù)加載策略。
7. 擴(kuò)展性較好(源于ASP.NET管線(xiàn)的擴(kuò)展性)
如果選擇使用Cookie實(shí)現(xiàn)會(huì)話(huà)狀態(tài),有3點(diǎn)需要特別注意:
1. 不建議保存敏感數(shù)據(jù),除非已加密。
2. 只適合保存短小簡(jiǎn)單的數(shù)據(jù)。
3. 如果會(huì)話(huà)數(shù)據(jù)較大,可以在客戶(hù)端保存用戶(hù)標(biāo)識(shí),由服務(wù)端實(shí)現(xiàn)數(shù)據(jù)的加載保存邏輯。
或許有些人認(rèn)為:每種技術(shù)都有它們的優(yōu)缺點(diǎn),有各自的適用領(lǐng)域。
我表示贊同這句話(huà)。
但是,我們要清楚一點(diǎn):每個(gè)項(xiàng)目的規(guī)模不一樣,性能以及擴(kuò)展性要求也不同。
對(duì)于一個(gè)小的項(xiàng)目來(lái)說(shuō),選擇什么方法都不是問(wèn)題,
但是,對(duì)于規(guī)模較大的項(xiàng)目,我們一定需要取舍。
取舍的目標(biāo)是:包裝越少越好,因?yàn)槿思易隽诉^(guò)多的包裝,就會(huì)有較多的限制,
所以,不要只關(guān)注現(xiàn)在的調(diào)用是否方便,其實(shí)只要你愿意包裝,你也可以讓復(fù)雜的調(diào)用簡(jiǎn)單化。
改變開(kāi)發(fā)方式,發(fā)現(xiàn)新方法
回想一下:為什么在ASP.NET中需要狀態(tài)管理?
答:因?yàn)榕cHTTP協(xié)議有關(guān),服務(wù)端沒(méi)有保存每個(gè)請(qǐng)求的上次頁(yè)面狀態(tài)。
為什么Windows計(jì)算器(這類(lèi))程序不用考慮會(huì)話(huà)問(wèn)題呢?
答:因?yàn)檫@類(lèi)程序的界面不需要重新生成,任何變量都可表示狀態(tài)。
再來(lái)看這樣一個(gè)場(chǎng)景:
圖片左邊是一個(gè)列表頁(yè)面,允許調(diào)整每條記錄的優(yōu)先級(jí),但是有2個(gè)要求:
1. 在移動(dòng)每條記錄時(shí),必須輸入一個(gè)調(diào)整理由。
2. 只要輸入理由后,那條記錄可以任意調(diào)整多次。
顯然,完成這個(gè)任務(wù)必須要有狀態(tài)才能實(shí)現(xiàn)。
面對(duì)這個(gè)問(wèn)題,你可以思考一下:選擇哪種ASP.NET支持的狀態(tài)管理方法都很麻煩。
怎么辦?
我的解決方法:創(chuàng)建一個(gè)JavaScript數(shù)組,用每個(gè)數(shù)組元素保存每條記錄的狀態(tài),
所有用戶(hù)交互操作用AJAX方式實(shí)現(xiàn),這樣頁(yè)面不會(huì)刷新,JavaScript變量中的狀態(tài)一直有效。
因此,很容易就能解決這個(gè)問(wèn)題。
這個(gè)案例也提醒我們:當(dāng)發(fā)現(xiàn)ASP.NET提供的狀態(tài)管理功能全部不合適時(shí), 我們需要改變開(kāi)發(fā)方式了。
為什么WEB編程都有【無(wú)狀態(tài)】問(wèn)題,而桌面程序沒(méi)有?
我認(rèn)為與HTTP協(xié)議有關(guān),但沒(méi)有絕對(duì)的關(guān)系。
只要你能保證頁(yè)面不刷新,也能像桌面程序那樣,用JavaScript變量就能維護(hù)頁(yè)面狀態(tài)。