在 Web 應用程序這樣的無狀態環境中,了解會話狀態的概念並沒有實際的意義。盡管如此,有效的狀態管理對於大多數 Web 應用程序來說都是一個必備的功能。Microsoft ASP.Net 以及許多其他服務器端編程環境都提供了一個抽象層,允許應用程序基於每個用戶和每個應用程序存儲持久性數據。
需要特別注意的是,Web 應用程序的會話狀態是應用程序在不同的請求中緩存和檢索的數據。會話表示用戶在與該站點連接期間發送的所有請求,會話狀態是用戶在會話期間生成和使用的持久性數據的集合。每個會話的狀態都彼此獨立,而且在用戶會話結束時就不復存在了。
會話狀態與構成 HTTP 協議和規范的任何邏輯實體都沒有對應關系。會話是由服務器端開發環境(例如傳統的 ASP 和 ASP.NET)構建的抽象層。ASP.NET 展示會話狀態的方式以及會話狀態的內部實現方式都取決於平台的基礎結構。因此,傳統的 ASP 和 ASP.NET 以完全不同的方式來實現會話狀態,預計在下一版的 ASP.Net 中會有進一步的改進和增強。
本文討論如何在 ASP.Net 1.1 中實現會話狀態,以及如何在被管理的 Web 應用程序中優化會話狀態管理。
ASP.Net 會話狀態概述
會話狀態並不是 HTTP 基礎結構的一部分。也就是說,應該有一個結構組件將會話狀態與每個傳入請求綁定在一起。運行時環境(傳統的 ASP 或 ASP.Net)能夠接受 Session 之類的關鍵字,並使用它指示服務器上存儲的數據塊。要成功解析 Session 對象的調用,運行時環境必須將會話狀態添加到正在處理的請求的調用上下文中。完成此操作的方式因平台而異,但它是有狀態 Web 應用程序的基礎操作。
在傳統的 ASP 中,會話狀態是作為 ASP.dll 庫中包含自由線程 COM 對象來實現的。(您對此很好奇嗎?其實該對象的 CLSID 是 D97A6DA0-A865-11cf-83AF-00A0C90C2BD8。)此對象存儲以名稱/值對集合的方式組織的數據。“名稱”占位符表示用來檢索信息的關鍵字,而“值”占位符表示會話狀態中存儲的內容。名稱/值對按照會話 ID 進行分組,這樣,每個用戶看到的只是他/她自己創建的名稱/值對。
在 ASP.NET 中,會話狀態的編程接口與傳統的 ASP 幾乎是相同的。但它們的基礎實現是完全不同的,前者比後者更具有靈活性、可擴展性和更強的編程功能。深入研究 ASP.NET 會話狀態之前,讓我們簡單回顧一下 ASP.Net 會話基礎結構的某些結構功能。
在 ASP.Net 中,任何傳入 HTTP 請求都要通過 HTTP 模塊管道進行傳輸。每個模塊都可以篩選並修改請求所攜帶的大量信息。與每個請求關聯的信息叫做“調用上下文”,編程中用 HttpContext 對象來表示。我們不應將請求的上下文視為狀態信息的另一個容器,雖然它提供的 Items 集合只是一個數據容器。HttpContext 對象不同於所有其他狀態對象(例如,Session、Application 和 Cache),因為它的有限生命周期超出了處理請求所需的時間。當請求通過一系列注冊的 HTTP 模塊後,其 HttpContext 對象將包含狀態對象的引用。當最終可以處理請求時,關聯的調用上下文將綁定到特定會話 (Session) 和全局狀態對象(Application 和 Cache)。
負責設置每個用戶的會話狀態的 HTTP 模塊為 SessionStateModule。該模塊的結構是根據 IHttpModule 接口設計的,它為 ASP.Net 應用程序提供大量與會話狀態有關的服務。包括生成會話 ID、CookIEless 會話管理、從外部狀態提供程序中檢索會話數據以及將數據綁定到請求的調用上下文。
HTTP 模塊並不在內部存儲會話數據。會話狀態始終保存在名為“狀態提供程序”的外部組件中。狀態提供程序完全封裝會話狀態數據,並通過 IStateClIEntManager 接口的方法與其他部分進行通信。會話狀態 HTTP 模塊調用該接口上的方法來讀取並保存會話狀態。ASP.Net 1.1 支持三種不同的狀態提供程序,如表 1 所示。
表 1:狀態客戶端提供程序
提供程序 說明
InProc 會話值在 ASP.Net 輔助進程(Microsoft? Windows Server? 2003 中的 ASPnet_wp.exe 或 w3wp.exe)的內存中保持為活動對象。這是默認選項。
StateServer 會話值被序列化並存儲在單獨進程 (ASPnet_state.exe) 的內存中。該進程還可以在其他計算機上運行。
SQLServer 會話值被序列化並存儲在 Microsoft? SQL Server? 表中。SQL Server 的實例可以在本地運行,也可以遠程運行。
會話狀態 HTTP 模塊將從 web.config 文件的 <sessionState>; 部分讀取當前選定的狀態提供程序。
<sessionState mode="InProc | StateServer | SQLServer />;
根據 mode 特性的值,將通過不同的步驟從不同的進程中檢索會話狀態並將其存儲到不同的進程中。默認情況下,會話狀態存儲在本地的 ASP.NET 輔助進程中。特殊情況下,會將其存儲在 ASP.Net Cache 對象的專用槽中(不能通過編程方式訪問)。也可以將會話狀態存儲在外部,甚至是遠程進程中(例如,名為 ASPnet_state.exe 的 Windows NT 服務中)。第三個選項是將會話狀態存儲到由 SQL Server 2000 管理的專用數據庫表中。
HTTP 模塊會在請求的一開始對會話值進行反序列化,使它們成為詞典對象。然後,將采用編程方式通過類(例如,HttpContext 和 Page)顯示的屬性 Session 來訪問詞典(實際上是 HttpSessionState 類型的對象)。會話狀態值與開發人員可見的會話對象之間的綁定將持續到請求結束。如果請求成功完成,所有狀態值將被序列化回狀態提供程序,並可用於其他請求。
圖 1 說明了請求的 ASP.Net 頁面與會話值之間的通信。每個頁面所使用的代碼都與 page 類上的 Session 屬性有聯系。其編程方式與傳統的 ASP 幾乎相同。
圖 1:ASP.Net 1.1 中的會話狀態體系結構
在完成請求所需的時間內,會話狀態的物理值處於鎖定狀態。該鎖定由 HTTP 模塊在內部管理並用於同步對會話狀態的訪問。
會話狀態模塊實例化應用程序的狀態提供程序,並使用從 web.config 文件中讀取的信息對其進行初始化。接下來,每個提供程序將繼續自己的初始化操作。提供程序的類型不同,其初始化操作會大不相同。例如,SQL Server 狀態管理器將打開與給定數據庫的連接,而進程外管理器將檢查指定的 TCP 端口。另一方面,InProc 狀態管理器將存儲對回調函數的引用。從緩存中刪除元素時將執行此操作,並用於觸發應用程序的 Session_OnEnd 事件。
同步訪問會話狀態
當 Web 頁對 Session 屬性進行非常簡單且直觀的調用時,究竟會出現什麼情況呢?許多操作都是在後台進行的,如下面的繁瑣代碼所示:
int siteCount = Convert.ToInt32(Session["Counter"]);
上述代碼實際上訪問的是 HTTP 模塊創建的會話值在本地內存中的副本,從特定狀態提供程序(參見圖 1)中讀取數據。如果其他頁面也試圖同步訪問該會話狀態,又會如何呢?這種情況下,當前的請求可能會停止處理不一致的數據或過時的數據。為了避免這種情況,會話狀態模塊將實現一個讀取器/寫入器鎖定機制,並對狀態值的訪問進行排隊。對會話狀態具有寫入權限的頁面將保留該會話的寫入器鎖定,直到請求終止。
通過將 @Page 指令的 EnableSessionState 屬性設置為 true,頁面可以請求會話狀態的寫入權限。(這是默認設置)。但是,頁面還可以擁有會話狀態的只讀權限,例如,當 EnableSessionState 屬性被設置為 ReadOnly 時。在這種情況下,模塊將保留該會話的讀取器鎖定,直到該頁面的請求結束。結果將發生並發讀取。
如果頁面請求設置一個讀取器鎖定,同一會話中同時處理的其他請求將無法更新會話狀態,但是至少可以進行讀取。也就是說,如果當前正在處理會話的只讀請求,那麼等候的只讀請求要比需要完全訪問權限的請求具有更高的優先權。如果頁面請求為會話狀態設置一個寫入器鎖定,那麼所有其他頁面都將被阻止,無論它們是否要讀取或寫入內容。例如,如果同時有兩個框架試圖在 Session 中寫入內容,一個框架必須等到另一個框架完成後才能寫入。
比較狀態提供程序
默認情況下,ASP.NET 應用程序將會話狀態存儲在輔助進程的內存中,特別是 Cache 對象的專用槽中。選中 InProc 模式時,會話狀態將存儲在 Cache 對象內的槽中。此槽被標記為專用槽,無法通過編程方式進行訪問。換句話說,如果枚舉 ASP.Net 數據緩存中的所有項目,將不會返回類似於給定會話狀態的任何對象。Cache 對象提供兩類槽:專用槽和公用槽。編程人員可以添加和處理公用槽,但專用槽只能由系統(特別是 system.web 部件中定義的類)專用。
每個活動會話的狀態都占用緩存中的一個專用槽。槽的名稱根據會話 ID 進行命名,其值是名為 SessionStateItem 的內部未聲明類的一個實例。InProc 狀態提供程序獲取會話 ID 並在緩存中檢索對應的元素。然後將 SessionStateItem 對象的內容輸入 HttpSessionState 詞典對象,並由應用程序通過 Session 屬性進行訪問。請注意,ASP.NET 1.0 中存在一個錯誤,使 Cache 對象的專用槽可以通過編程方式進行枚舉。如果您在 ASP.Net 1.0 下運行以下代碼,則能夠枚舉與每個當前活動會話狀態中包含的對象對應的項目。
foreach(DictionaryEntry elem in Cache)
{
Response.Write(elem.Key + ": " + elem.Value.ToString());
}
此錯誤已經在 ASP.Net 1.1 中得到解決,當您枚舉緩存的內容時,將不再列出任何系統槽。
到目前為止,InProc 可能是最快的訪問選項。但請記住,會話中存儲的數據越多,Web 服務器所消耗的內存就越多,這樣會潛在地增加性能降低的風險。如果您計劃使用任何進程外解決方案,應該認真考慮一下序列化和反序列化可能帶來的影響。進程外解決方案使用 Windows NT 服務 (aspnet_state.exe) 或 SQL Server 表來存儲會話值。因此,會話狀態保留在 ASP.Net 輔助進程之外,並且需要使用額外的代碼層,在會話狀態和實際的存儲介質之間進行序列化和反序列化操作。只要處理請求就會發生此操作,而且隨後必須對其進行最高程度的優化。
因為需要將會話數據從外部儲備庫復制到本地會話詞典中,所以請求導致性能下降了 15%(進程外)到 25% (SQL Server)。請注意,雖然這只是一種粗略的估計,但它應該接近於最低程度的影響,最高程度的影響將遠高於此。實際上,這種估計並沒有完全考慮到會話狀態中實際保存的類型的復雜程度。
在進程外存儲方案中,會話狀態存活的時間較長,使應用程序的功能更強大,因為它可以防止 Microsoft? Internet 信息服務 (IIS) 和 ASP.Net 失敗。通過將會話狀態與應用程序相分離,您還可以更容易地將現有應用程序擴展到 Web Farm 和 Web Garden 體系結構中。另外,會話狀態存儲在外部進程中,從根本上消除了由於進程循環而導致的周期性數據丟失的風險。
下面介紹如何使用 Windows NT 服務。正如上文所述,NT 服務是一個名為 ASPnet_state.exe 的進程,通常位於 C:\WINNT\Microsoft.Net\Framework\v1.1.4322 文件夾中。
實際目錄取決於您實際運行的 Microsoft? .NET Framework 版本。使用狀態服務器之前,應確保該服務就緒並正運行在用作會話存儲設備的本地或遠程計算機上。狀態服務是 ASP.NET 的組成部分並與之一起安裝,因此您無需運行其他安裝程序。默認情況下,狀態服務並沒有運行,需要手動啟動。ASP.Net 應用程序將在加載狀態服務器之後立即嘗試與之建立連接。因此,該服務必須准備就緒且正在運行,否則將引發 HTTP 異常。下圖顯示了該服務的屬性對話框。
圖 2:ASP.Net 狀態服務器的屬性對話框
ASP.Net 應用程序需要指定會話狀態服務所在的計算機的 TCP/IP 地址。必須將以下設置輸入該應用程序的 web.config 文件中。
<configuration>;
<system.web>;
<sessionState
mode="StateServer"
stateConnectionString="tcpip=expoware:42424" />;
</system.web>;
</configuration>;
stateConnectionString 特性包含計算機的 IP 地址以及用來進行數據交換的端口。默認的計算機地址為 127.0.0.1(本地主機),默認端口為 42424。您也可以按名稱指示計算機。對於代碼來說,使用本地或遠程計算機是完全透明的。請注意,不能在該名稱中使用非 ASCII 字符,並且端口號是強制的。
如果您使用進程外會話存儲,會話狀態將仍然存在並且可供將來使用,無論 ASP.Net 輔助進程出現何種情況。如果該服務被中斷,數據將被保留下來,並且在該服務恢復時自動進行檢索。但是,如果狀態提供程序服務停止或失敗,數據將丟失。如果您希望應用程序具有強大的功能,請使用 SQLServer 模式,而不要使用 StateServer 模式。
<configuration>;
<system.web>;
<sessionState
mode="SQLServer"
sqlConnectionString="server=127.0.0.1;uid=<user id>;;pwd=<passWord>;;" />;
</system.web>;
</configuration>;
您可以通過 sqlConnectionString 特性指定連接字符串。請注意,特性字符串必須包含用戶 ID、密碼和服務器名稱。它不能包含 Database 和 Initial Catalog 之類的標記,因為此信息默認為固定名稱。用戶 ID 和密碼可以替換為集成的安全設置。
如何創建數據庫?ASP.Net 提供兩對腳本來配置數據庫環境。第一對腳本名為 InstallSqlState.sql 和 UninstallSqlState.sql,與會話狀態 NT 服務位於同一個文件夾中。它們創建名為 ASPState 的數據庫和幾個存儲的過程。但是,數據存儲在 SQL Server 臨時存儲區域 TempDB 數據庫中。這意味著,如果重新啟動 SQL Server 計算機,會話數據將丟失。
要解決這一局限性,請使用第二對腳本。第二對腳本名為 InstallPersistSqlState.sql 和 UninstallPersistSqlState.sql。在這種情況下,將創建 ASPState 數據庫,但是會在同一個數據庫中創建數據表,而且這些數據表同樣是持久的。為會話安裝 SQL Server 支持時,還將創建一個作業,以刪除會話狀態數據庫中過期的會話。該作業名為 ASPState_Job_DeleteExpiredSessions 並且一直運行。請注意,要使該作業正常進行,需要運行 SQLServerAgent 服務。
無論您選擇哪種模式,為會話狀態操作進行編碼的方式都不會改變。您可以始終針對 Session 屬性進行工作並像平常一樣讀取和寫入值。所有行為上的差異都是在較低的抽象層上處理的。狀態序列化或許是會話模式之間的最重要差異。
狀態序列化和反序列化
使用進程內模式時,對象作為各自類的活動實例存儲在會話狀態中。如果未發生真正的序列化和反序列化,則表示您實際上可以在 Session 中存儲您創建的任何對象(包括無法序列化的對象和 COM 對象),並且訪問它們的開銷也不會太高。如果您選擇進程外狀態提供程序,又是另外一種情況。
在進程外體系結構中,會話值將從本地存儲介質(外部 AppDomain 數據庫)復制到處理請求的 AppDomain 的內存中。需要使用序列化/反序列化圖層完成該任務,並表示進程外狀態提供程序的某項主要成本。這種情況對代碼產生的主要影響是只能在會話詞典中存儲可序列化的對象。
根據所涉及的數據類型,ASP.NET 使用兩種方法對數據進行序列化和反序列化。對於基本類型,ASP.NET 使用經過優化的內部序列化程序;對於其他類型(包括對象和用戶定義的類),ASP.NET 使用 .NET 二進制格式化程序。基本類型包括字符串、日期時間、布爾值、字節、字符以及所有的數字類型。對於這些類型,使用量身制作的序列化程序要比使用默認的常用 .Net 二進制格式化程序更快。
經過優化的序列化程序沒有公開發布,也沒有以文檔形式提供。它僅僅是二進制讀取器/寫入器,並且使用簡單但有效的存儲架構。該序列化程序使用 BinaryWriter 類寫入一個字節表示類型,然後寫入一個字節表示該類型對應的值。讀取序列化的字節時,該類首先提取一個字節,檢測要讀取的數據類型,然後對 BinaryReader 類調用特定類型的 ReadXxx 方法。
請注意,布爾值和數字類型的大小是眾所周知的,但對字符串並非如此。在基礎數據流上,字符串始終帶有一個固定長度的前綴(一次編寫 7 位整數代碼),讀取器根據這一事實來確定字符串的正確大小。而日期值是通過只寫入構成日期的標記總數來保存的。因此,要對會話執行序列化操作,日期應為 Int64 類型。
只要將包含的類標記為可序列化的類,便可以使用 BinaryFormatter 類對更復雜的對象(以及自定義對象)執行序列化操作。所有非基本類型都采用相同的類型 ID 進行標識並與基本類型存儲在同一個數據流中。總之,序列化操作會導致性能下降 15% 至 25%。但請注意,這是基於假定使用基本類型所進行的粗略估計。使用的類型越復雜,開銷越大。
如果不大量使用基本類型,很難實現有效的會話數據存儲。因此,至少在理論上,使用三個會話槽保存對象的三個不同的字符串屬性要比對整個對象進行序列化好。但是,如果要序列化的對象包含 100 個屬性,那該怎麼辦呢?是要使用 100 個槽,還是只使用一個槽?在許多情況下,更好的方法是將復雜的類型轉換為多個簡單的類型。這種方法基於類型轉換器。“類型轉換器”是一種輕便的序列化程序,它以字符串集合的形式返回類型的關鍵屬性。類型轉換器是使用特性與基類綁定在一起的外部類。由類型編寫者決定保存哪些屬性以及如何保存。類型轉換器對於 VIEwState 存儲也有幫助,它代表的是比二進制格式化程序更有效的會話存儲方法。
會話的生命周期
關於 ASP.NET 會話管理,重要的一點是,僅當將第一個項目添加到內存詞典中時,會話狀態對象的生命周期才開始。僅在執行如下代碼片斷後,才可以認為 ASP.Net 會話開始。
Session["MySlot"] = "Some data";
Session 詞典通常包含 Object 類型,要向後讀取數據,需要將返回的值轉換為更具體的類型。
string data = (string) Session["MySlot"];
當頁面將數據保存到 Session 中時,會將值加載到 HttpSessionState 類包含的特制的詞典類中。完成當前處理的請求時,會將詞典的內容加載到狀態提供程序中。如果由於未通過編程方式將數據放入詞典而導致會話狀態為空,則不會將數據序列化到存儲介質中,而且更重要的是,不會在 ASP.Net Cache、SQL Server 或 NT 狀態服務中創建槽來跟蹤當前會話。這是出於性能方面的原因,但會對處理會話 ID 的方式產生重要影響:將為每個請求生成一個新的會話 ID,直到將某些數據存儲到會話詞典中。
需要將會話狀態與正在處理的請求連接時,HTTP 模塊會檢索會話 ID(如果它不是啟動請求),並在配置的狀態提供程序中尋找它。如果沒有返回數據,HTTP 模塊將為請求生成一個新的會話 ID。這可以很容易地通過以下頁面進行測試:
<%@ Page Language="C#" Trace="true" %>;
</Html>;
<body>;
<form runat="server">;
<ASP:button runat="server" text="Click" />;
</form>;
</body>;
</Html>;
無論何時單擊該按鈕並返回頁面,都將生成新的會話 ID,同時記錄跟蹤信息。
圖 3:在沒有將數據存儲到會話詞典中的應用程序中,為每個請求生成一個新的會話 ID。
Session_OnStart 事件的情況如何呢?也會為每個請求引發該事件嗎?如果應用程序定義 Session_OnStart 處理程序,則會始終保存會話狀態,即使會話狀態為空。因此,對於第一個請求之後的所有請求來說,會話 ID 始終為常量。僅在確實必要時,才使用 Session_OnStart 處理程序。
如果會話超時或被放棄,下次訪問無狀態應用程序時,其會話 ID 不會發生改變。經過設計後,即使會話狀態過期,會話 ID 也能持續到浏覽器會話結束。也就是說,只要浏覽器實例相同,就始終使用同一個會話 ID 表示多個會話。
Session_OnEnd 事件標志著會話的結束,並用於執行終止該會話所需的所有清除代碼。但請注意,只有 InProc 模式支持該事件,也就是說,只有將會話數據存儲在 ASP.Net 輔助進程中時才支持該事件。對於要引發的 Session_OnEnd 事件來說,必須首先存在會話狀態,這意味著必須在該會話狀態中存儲一些數據,並且必須至少完成一個請求。
在 InProc 模式下,作為項目添加到緩存中的會話狀態被賦予一個可變過期時間策略。可變過期時間表示如果某個項目在一定時間內沒有使用,將被刪除。在此期間處理的任何請求的過期時間都將被重置。會話狀態項目的時間間隔被設置為會話超時。用來重置會話狀態過期時間的技術非常簡單和直觀:會話 HTTP 模塊只讀取 ASP.NET Cache 中存儲的會話狀態項目。如果知道 ASP.Net Cache 對象的內部結構,該模塊將進行計算以重新設置可變過期時間。因此,當緩存項目過期時,會話已超時。
過期的項目將自動從緩存中刪除。狀態會話模塊作為此項目的過期時間策略的一部分,也代表了一個刪除回調函數。緩存將自動調用刪除函數,刪除函數然後將引發 Session_OnEnd 事件。如果應用程序通過進程外組件來執行會話管理,則永遠不會引發結束事件。
CookIEless 會話
每個活動 ASP.Net 會話都是使用僅由 URL 允許的字符組成的 120 位字符串標識的。會話 ID 是使用隨機數生成器 (RNG) 加密提供程序生成的。該服務提供程序返回一個包含 15 個隨機生成數的序列(15 字節 x 8 位 = 120 位)。隨機數數組然後被映射到有效的 URL 字符並以字符串形式返回。
會話 ID 字符串被發送到浏覽器,然後通過以下兩種方式之一返回服務器應用程序:使用 CookIE(就像在傳統 ASP 中一樣)或經過修改的 URL。默認情況下,會話狀態模塊將在客戶端創建 HTTP Cookie,但是可以使用嵌入會話 ID 字符串的修改後的 URL(特別是對於不支持 CookIE 的浏覽器)。采用哪種方法取決於應用程序的 web.config 文件中所存儲的配置設置。要配置會話設置,可以使用 <sessionState>; 區段和 CookIEless 特性。
<sessionState cookIEless="true|false" />;
默認情況下,Cookieless 特性為 false,表示使用了 Cookie。實際上,Cookie 只是 Web 頁放在客戶端硬盤上的一個文本文件。在 ASP.NET 中,Cookie 由 HttpCookie 類的一個實例來表示。通常,Cookie 包含名稱、值集合和過期時間。CookIEless 特性被設置為 false 時,會話狀態模塊實際上將創建一個名為 ASP.Net_SessionId 的 CookIE 並將會話 ID 存儲在其中。下面的偽代碼顯示了創建 CookIE 的過程:
HttpCookie sessionCookIE;
sessionCookie = new HttpCookIE("ASP.Net_SessionId", sessionID);
sessionCookIE.Path = "/";
會話 Cookie 的過期時間很短,在每個請求成功後更新過期時間。Cookie 的 Expires 屬性表示 Cookie 在客戶端的過期時間。如果未顯式設置會話 CookIE,Expires 屬性將默認為 DateTime.MinValue,即 .Net Framework 允許的最小時間單位。
要禁用會話 Cookie,請在配置文件中將 CookIEless 特性設置為 true,如下所示:
<configuration>;
<system.web>;
<sessionState CookIEless="true" />;
</system.web>;
</configuration>;
此時,假設您請求以下 URL 處的頁面:
http://www.contoso.com/sample.ASPx
浏覽器地址欄中實際顯示的內容會有所不同,現在包含會話 ID,如下所示:
http://www.contoso.com/(5ylg0455mrvws1uz5mmaau45)/sample.ASPx
實例化會話狀態 HTTP 模塊時,該模塊將檢查 CookIEless 特性的值。如果為 true,則將請求重定向 (HTTP 302) 到經過修改的包含會話 ID(緊跟在頁面名稱前)的虛擬 URL。再次處理請求時,請求中會包含該會話 ID。如果請求啟動新的會話,HTTP 模塊將生成新的會話 ID,然後重定向該請求。如果回傳請求,則會話 ID 已經存在,因為回傳使用相對 URL。
使用 Cookieless 會話的缺點是,如果調用絕對 URL,將丟失會話狀態。使用 Cookie 時,您可以清除地址欄,轉至其他應用程序,然後返回上一個應用程序並檢索相同的會話值。如果在禁用會話 CookIE 時執行此操作,將丟失會話數據。例如,以下代碼將打斷該會話:
<a runat="server" href="/code/page.ASPx">;Click</a>;
如果需要使用絕對 URL,請通過一些小技巧手動將會話 ID 添加到 URL 中。您可以對 HttpResponse 類調用 ApplyAppPathModifIEr 方法。
<a runat="server"
href=<% =Response.ApplyAppPathModifIEr("/code/page.ASPx")%>; >;Click</a>;
ApplyAppPathModifIEr 方法將使用表示 URL 的字符串,並返回嵌入會話信息的絕對 URL。例如,需要從 HTTP 頁面重定向到 HTTPS 頁面時,此技巧特別有用。
小結
會話狀態最初由傳統的 ASP 引入,它是基於詞典的 API,使開發人員能夠存儲會話期間的自定義數據。在 ASP.NET 中,會話狀態支持以下兩種主要功能:CookIEless 會話 ID 存儲和傳輸,以及會話數據實際存儲的狀態提供程序。為實現這兩種新功能,ASP.Net 利用 HTTP 模塊控制會話狀態與正在處理的請求上下文之間的綁定。
在傳統的 ASP 中,使用會話狀態就是指使用 CookIE。在 ASP.Net 中已不再如此,因為可以使用 CookIEless 架構。借助 HTTP 模塊的力量,可以分解請求的 URL 以使其包含會話 ID,然後將其重定向。接下來,HTTP 模塊會從該 URL 中提取會話 ID 並使用它檢索任何存儲的狀態。
會話的物理狀態可以存儲在三個位置:進程內內存、進程外內存和 SQL Server 表。數據必須經過序列化/反序列化處理,才能供應用程序使用。HTTP 模塊會在請求開始時將會話值從提供程序復制到應用程序的內存中。請求完成後,修改後的狀態將返回提供程序。這種數據通信會對性能產生不同程度的不利影響,但是會大大增強可靠性和穩定性,也使對 Web Farm 和 Web Garden 體系結構的支持更容易實現。