對於工作階段狀態值的儲存,HttpSessionState類提供了一個基於字典的模型。只有同一會話上下文中的請求(即由同一個使用者發出的多個頁面請求)才可以訪問工作階段狀態。工作階段狀態的儲存和發布方式有很多,其中包括在Web Farm和Web Garden環境下的。但在預設情況下,工作階段狀態由ASP.NET背景工作執行緒持有。
工作階段狀態的可擴充模型提供了兩種方案:
1. 可對ASP.NET現有的工作階段狀態機制進行局部的自訂(如:建立Oracle會話提供者或建立能控制ID產生的模組)。
2. 可以將整個標準的工作階段狀態HTTP模組替換成其他的自訂工作階段狀態HTTP模組。
第一種方案更容易實現,但可自訂的功能比較有限。第二種方案的編碼較複雜,但提供更好的靈活性。
工作階段狀態HTTP模組
工作階段狀態是一個集合,由Page類的Session屬性暴露出來,其類型為HttpSessionState。該類是封閉的,實現了ICollection和IEnumerable介面。該類的執行個體會在每個支援會話的請求啟動時建立。該集合填充的資料會以鍵/值對的形式從指定的介質中讀取,之後會被附加到請求上下文上。Page的Session屬性只是映射到HttpContext類的Session屬性而已。
HTTP模組通過某些特殊的提供者來管理工作階段狀態的儲存與擷取。負責為每個串連到應用程式的使用者建立工作階段狀態的ASP.NET模組,是一個被稱為SessionStateModule的HTTP模組。SessionStateModule對象實現了IHttpModule介面,提供了ASP.NET應用程式的工作階段狀態服務。
但作為HTTP模組,SessionStateModule需要通過相對簡單的編程介面(IHttpModule介面只有Init和Dispose方法)來執行許多非常複雜的任務,而且其中的許多操作會嚴重影響Web應用程式的屬性和功能。工作階段狀態模組會在即將處理某個給定請求的HttpApplication對象建立的過程中調用,該模組需要負責產生或擷取一個唯一會話ID字串,以便通過狀態供應器(如SQL Server或Web伺服器的記憶體)來儲存和擷取狀態資料。
狀態用戶端管理器
當工作階段狀態HTTP模組被調用時,它會讀取web.config檔案<sessionState>區段中的設定,並決定當前應用程式選用何種“狀態用戶端管理器”。狀態用戶端管理器是一種負責存/取當前活動會話資料的組件。SessionStateModule組件會對狀態用戶端管理器進程進行查詢,來獲得給定會話的鍵/值對。
ASP.NET中有4種工作階段狀態的處理方式。工作階段狀態可儲存在ASP.NET背景工作執行緒中,可以在一個叫aspnet_state.exe外部(甚至是遠程)進程中維護,可以由通過SQL Server或其他資料庫表來管理。最後一種方式是將會話資料存放區在自訂的組件中。下表對各種方案做了總結:
SessionStateMode枚舉類型包含狀態用戶端提供者可用的選項。InProc選項的訪問速度最快,但應記住,在會話中儲存的資料越多,Web伺服器記憶體的佔用量也越大,從而會造成效能瓶頸。如果打算採用進程外(out-of-process)解決方案,則應考慮序列化與還原序列化過程帶來的影響。
工作階段狀態模組會基於web.config檔案<sessionState>區段讀取的設定來確定狀態供應器。接著,它會對應用程式的狀態供應器進行執行個體化和初始化。每種提供者自身能夠執行初始化,不同類型的提供者該過程會有所差異。
所有狀態供應器都暴露了用於與主調程式進行通訊的方法,整個架構如所示:
所有實際的狀態供應器對象都統一繼承於一個基類:SessionStateStoreProviderBase類。
HttpSessionState對象的建立
狀態模組負責工作階段狀態的擷取,並將其附加到當前會話中啟動並執行每個請求上下文中。工作階段狀態在HttpApplication.AcquireRequestState事件引發後才可用,並在HttpApplication.ReleaseRequestState事件引發後將資料丟棄且不可恢複。這意味著隨後引發Session_End事件時,所有狀態都已消失。
在HttpApplication.AcquireRequestState事件引發時,會話模組會為請求建立HttpSessionState對象。此時,HttpSessionStae對象會獲得一個會話ID和一個會話字典。會話字典實際上是一種狀態值得的集合,可通過頁面的Session屬性訪問。
在剛剛發起新會話時,資料字典是一個空的對象。如果該模組處理的是現有會話的請求,那麼當前活動的工作階段狀態提供者會對資料進行還原序列化,並填充該資料字典。在請求結束時,若頁面請求修改當前字典的內容,那麼它會執行序列化過程,並將資料發送給狀態供應器。整個過程如所示:
工作階段狀態訪問的同步
在Web頁面調用Session屬性時,它實際上訪問的是資料的副本,位於本地的記憶體中。如果其他頁面(同一會話中的)試圖以並發方式訪問工作階段狀態,會發生什麼情況?在這種情況下,當前請求最終可能會獲得不一致的資料,或者說資料不是最新的。
為避免這種情況的發生,工作階段狀態模組實現了讀取方法和寫入方法的鎖定機制,使要訪問狀態資料的請求排隊執行。對工作階段狀態寫入訪問的頁面會保持寫入方法在當前會話中處於鎖定狀態,直到當前請求結束。通過將@Page指令的EnableSessionState屬性設定為true,頁面便獲得工作階段狀態的寫入存取權限。只擁有工作階段狀態讀取存取許可權的頁面(如EnableSessionState屬性為ReadOnly)會保持讀取方法在當前會話中處於鎖定狀態,直到當前請求結束。
如果頁面請求設定了讀取方法的鎖(reader lock),其他並發啟動並執行請求則不能更新工作階段狀態,但允許讀取。如果頁面請求對工作階段狀態設定了編寫器的鎖(writer lock),其他頁面不論是讀取還是寫入,都會被鎖定。
工作階段狀態的並發訪問在實際當中並不常見。若某個頁面帶有多個架構(frame),或使用者開啟了同一頁面的兩個副本或同一應用程式的多個頁面,可能會發生這種情況。此外,若使用啟用會話的HTTP處理常式對一個內嵌資源(映像或CSS檔案)進行服務時,也可能發生。在預設情況下,並發訪問是不會發生的。然而,顯式地聲明頁面對工作階段狀態的使用模式(讀/寫、唯讀或不使用),不失為一種好的編程習慣。為此,只需設定@Page指令的EnableSessionState屬性即可。
HttpSessionStae類的屬性
HttpSessionState類定義在System.Web.SessionState命名空間中。這是一個泛型集合類,實現了ICollection介面。下表列出了HttpSessionState類的屬性:
出於同步方面的考慮使HttpSessionState類成為非常特殊的集合類。如前所述,同步機制是在SessionStateModule組件中實現的,該組件確保同一時刻只有一個線程可以訪問工作階段狀態。然而,由於HttpSessionState實現了ICollection介面,包含了對IsSynchronized和SyncRoot的實現。應注意,IsSynchronized和SyncRoot是同步集合特有的屬性,與之前所討論的會話同步毫不相干。從技術角度講,HttpSessionState本身不支援同步,但通過它訪問的工作階段狀態是同步的。
HttpSessionState類的方法
下表列出了HttpSessionState類的方法:
當運行終止當前請求的過程時,工作階段狀態模組會檢查一下內部狀態,確認使用者是否發出取消會話的命令。如果該標誌被設定(Abandon方法被調用),所有響應cookie都會被移除,終止會話的過程會被啟動。但那並不意味著Session_End事件一定會被觸發。
首先,僅當會話模式為InProc時,Session_End事件才會被觸發。其次,如果會話字典為空白,且應用程式中不存在實際的工作階段狀態資料,該事件則不會被觸發。換言之,若會話自然關閉或調用過Abandon,且至少有一個請求已完成,Session_End才會被觸發。