海水正藍

          面朝大海,春暖花開
          posts - 145, comments - 29, trackbacks - 0, articles - 1
            BlogJava :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          【轉】ASP.NET狀態(tài)管理的總結

          Posted on 2012-12-08 17:44 小胡子 閱讀(253) 評論(0)  編輯  收藏

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

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

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

          hidden-input

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

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

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

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

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

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

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

          輸出結果:

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

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

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

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

          QueryString

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

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

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

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

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

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

          Cookie

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

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

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

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

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

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

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

          ApplicationState

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

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

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

          ViewState,ControlState

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

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

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

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

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

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

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

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

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

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

          Session

          Session是ASP.NET實現的一種服務端會話技術,它允許我們方便地在服務端保存與用戶有關的會話數據。

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

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

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

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

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

          我認為了解Sesssion本質非常有用,因為可以借鑒并實現自己的服務端會話方法。

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

          Profile

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

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

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

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

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

          為什么會這樣呢?
          原因是ASP.NET已經根據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); } 

          有了這個類型后,當我們訪問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表,增加用戶信息時,會通過增加字段的方式解決。
          我認為這樣集中的數據才會更好,而不是說,有一部分數據由我維護,另一部分數據由ASP.NET維護。

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

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

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

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


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

          表格中主要考察了數據保存與服務端水平擴展的相關重要指標。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          再來看這樣一個場景:

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

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

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

          怎么辦?

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

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

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

          原文出自:
          http://www.cnblogs.com/fish-li/archive/2012/11/21/2780086.html

          只有注冊用戶登錄后才能發(fā)表評論。


          網站導航:
           
          主站蜘蛛池模板: 黄大仙区| 于田县| 榆树市| 和顺县| 米泉市| 郸城县| 湘乡市| 平安县| 南涧| 四平市| 宁波市| 明溪县| 福安市| 南靖县| 涡阳县| 清水河县| 黄浦区| 偏关县| 若羌县| 湖南省| 拜泉县| 那曲县| 尉氏县| 安多县| 金华市| 收藏| 昭觉县| 枣阳市| 香港| 南陵县| 元阳县| 阜城县| 济南市| 德阳市| 高碑店市| 宁国市| 商河县| 瓦房店市| 松溪县| 抚松县| 威海市|