*
   原稿 (英文)

ASP.NET 裡的工作階段狀態實作的基礎

作者:Dino Esposito
Wintellect

2003 年 9 月

適用於:
Microsoft® ASP.NET

摘要: 討論 ASP.NET 1.1 之工作階段狀態功能和機能的實作,以及如何從 Managed Web 應用程式裡最佳化工作階段狀態。(列印共 13 頁,本文包含至英文網頁連結。)

目錄

簡介
ASP.NET 工作階段狀態概觀
同步對工作階段狀態的存取
比較狀態提供者
狀態序列化和還原序列化
工作階段的存留期
無 Cookie 工作階段
總結

簡介

在 Web 應用程式這類無狀態的環境中,工作階段狀態的概念應該是完全沒什麼意義。 儘管如此,大多數 Web 應用程式仍需要有效的狀態管理。 Microsoft® ASP.NET 和其他許多伺服器端的程式設計環境一樣,提供抽象層,讓應用程式依照每個使用者和應用程式的基礎儲存永續性資料。

而如果 Web 應用程式的工作階段狀態來自應用程式跨各要求所快取和擷取的資料,更是需要這個抽象層。 工作階段代表使用者在連線持續期間傳送到網站的所有要求,而工作階段狀態是使用者在工作階段期間產生和使用的永續性資料的集合。 每個工作階段的狀態與其他工作階段的狀態無關,在使用者工作階段結束後也不會存留。

工作階段狀態與構成 HTTP 通訊協定和規格的任何邏輯實體不一樣。 工作階段是由伺服器端開發環境 (例如典型的 ASP 和 ASP.NET) 建置的抽象層。不論是哪一種方式,工作階段狀態都是由 ASP.NET 公開,而其在內部實作的方式則取決於平台的基礎。 因此,現階段的 ASP 和 ASP.NET 是以完全不同的方式實作工作階段狀態,預計下一版的 ASP.NET 會有更多的變更和加強。

本文將探討 ASP.NET 1.1 裡工作階段狀態實作的步驟,以及從 Managed Web 應用程式中最佳化工作階段狀態管理的方法。

ASP.NET 工作階段狀態概觀

工作階段狀態並非 HTTP 基礎結構的原來就有的設計。 這表示必須有一個架構性元件來繫結工作階段狀態與每個內送要求。 執行環境 (典型的 ASP 或 ASP.NET) 接受 Session 等關鍵字,並令其指示儲存在伺服器上某處的資料區塊。 若要順利解析任何對 Session 物件的呼叫,執行環境必須新增工作階段狀態至所處理之要求的呼叫內容。 發生的方式乃依平台而異,不過這是有狀態 Web 應用程式的基礎步驟。

在典型 ASP 中,工作階段狀態是當成 asp.dll 程式庫中包含的無限制執行緒 COM 物件實作 (滿足一下諸位的好奇心,物件的 CLSID 是 D97A6DA0-A865-11cf-83AF-00A0C90C2BD8)。 這個物件會儲存資料組織為名稱/值配對集合的資料。 name 預留位置指示擷取資訊的關鍵字;value 預留位置則代表儲存在工作階段狀態中的內容。 名稱/值配對會依工作階段識別碼分組,讓每個使用者只看到自己建立的配對。

ASP.NET 的工作階段狀態程式設計介面幾乎與典型的 ASP 相同。 不過根本的實作則完全不同, 而且提供更大的彈性、可調整性和程式設計功能。 然而,在深入探索 ASP.NET 工作階段狀態之前,還是要簡要地重新檢視 ASP.NET 工作階段基礎結構的一些架構性功能。

ASP.NET 裡任何內送的 HTTP 要求都是透過 HTTP 模組的管線傳遞。 每個模組都可以篩選並修改要求傳送的資訊量。 與每個要求關聯的資訊稱為「呼叫內容」,在程式中以 HttpContext 物件代表。 雖然要求的內容提供 Items 集合 (這只是一個資料容器),但是不應視為狀態資訊的另一個容器。 HttpContext 物件與其他所有狀態物件 (SessionApplicationCache) 的不同點在於,它的有限存留期超過處理要求所需的時間。 當要求在已登錄的 HTTP 模組鏈中傳遞時,會給予其 HttpContext 物件對狀態物件的參考。 等到要求準備好接受處理時,相關的呼叫內容就會繫結至工作階段特定 (Session) 和全域狀態物件 (ApplicationCache)。

負責設定每位使用者之工作階段狀態的 HTTP 模組是 SessionStateModule。 在 IHttpModule 介面之後結構化的模組,提供各種工作階段狀態–ASP.NET 應用程式的相關服務。 提供的服務包括工作階段識別碼產生、無 Cookie 工作階段管理、從外部狀態提供者擷取工作階段資料,以及將資料繫結至要求的呼叫內容。

HTTP 模組內部不會儲存工作階段資料。 工作階段狀態是存留在名為 state providers 的外部元件裡。 狀態提供者會完全封裝工作階段狀態資料,並透過 IStateClientManager 介面的方法與其他部份通訊。 工作階段狀態 HTTP 模組會呼叫介面上的方法讀取及儲存工作階段的狀態。 ASP.NET 1.1 支援三種不同的狀態提供者,如 [表 1] 所列。

[表 1] 狀態用戶端提供者

提供者 說明
InProc 工作階段值會以作用中物件的形式保存在 ASP.NET 背景工作處理序 (Microsoft® Windows Server™ 2003 裡為 aspnet_wp.exew3wp.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 模組會在要求開始時將工作階段值還原序列化到字典物件中。 然後再透過 HttpContextPage 等類別公開的屬性 Session,讓程式能夠存取字典,也就是 HttpSessionState 型別的物件。 工作階段狀態值和開發人員可見的工作階段物件之間的繫結會一直持續到要求結束為止。 如果要求順利完成,則所有狀態值都會序列化回狀態提供者,供其他要求使用。

[圖 1] 說明要求的 ASP.NET 網頁和工作階段值之間的通訊。 每個網頁使用的程式碼會連接 page 類別上的 Session 屬性。 程式設計樣式幾乎和典型的 ASP 相同。

[圖 1] ASP.NET 1.1 裡的工作階段狀態架構

工作階段狀態的實體值會在完成要求所需的時間內鎖定。 鎖定是由 HTTP 模組內部進行管理,並用於同步工作階段狀態的存取。

工作階段狀態模組會針對應用程式執行狀態提供者個體化,並利用從 web.config 檔案讀取的資訊將其初始化。 然後,每個提供者繼續本身的初始化,其情形依據類型大有不同。 例如,SQL Server 狀態管理員會開啟連至指定資料庫的連線,而跨處理序 (Out-Of-Process) 管理員則會檢查指定的 TCP 埠。 另一方面,InProc 狀態管理員則會將參考儲存至回呼函式 (Callback Function)。 從快取移除項目時,此函式就會執行,並用於引發對應用程式的 Session_OnEnd 事件。

同步對工作階段狀態的存取

那麼當網頁對 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 組件裡定義的類別) 則將私用位置保留給自己使用。

每個作用中工作階段的狀態都佔有快取中的一個私用位置。 工作階段識別碼和值之後指名的位置是一個內部未記載類別 (名為 SessionStateItem) 的執行個體。 InProc 狀態提供者會以工作階段的識別碼,從快取中擷取對應的項目。 然後再將 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 Information Services (IIS) 和 ASP.NET 發生失敗。 將工作階段狀態與應用程式本身分開,也更容易調整現有應用程式至 Web Farm 和 Web 處理序區 (Web Garden) 架構。 此外,存留在外部處理序裡的工作階段狀態也不會因為處理序回收處理而週期性地失去資料。

現在來探討一下 Windows NT 服務的使用情形。 前面提過,這是一個名為 aspnet_state.exe 的處理序,通常存放在 C:\WINNT\Microsoft.NET\Framework\v1.1.4322 資料夾裡。

實際的字典會依您實際執行的 Microsoft® .NET Framework 版本而定。 使用狀態伺服器之前,應確定服務處於運作的狀態,並且在作為工作階段儲存區的本機或遠端電腦上執行。 狀態服務是 ASP.NET 的構成部分,會和 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 背景工作處理序的工作情形為何,都會保持工作階段狀態以供進一步使用。 若是暫停服務,將會保留資料,等到服務繼續後就會自動擷取。 但是狀態提供者服務若是停止或失敗,就會失去資料。 如果穩定對您的應用程式來說很重要,則停止 StateServer 模式將有利於 SQLServer

<configuration>
    <system.web>
        <sessionState 
            mode="SQLServer" 
            sqlConnectionString="server=127.0.0.1;uid=<user id>;pwd=<password>;" />
    </system.web>
</configuration>

您是透過 sqlConnectionString 屬性來指定連接字串。 請注意,屬性字串必須包含使用者識別碼、密碼和伺服器名稱。 其中不能包含語彙基元,例如 Database 和 Initial Catalog,因為此一資訊預設為固定的名稱。 使用者識別碼和密碼可由整合式安全性設定取代。

資料庫是如何建立的? ASP.NET 提供兩對指令碼以設定資料庫環境。 第一對的指令碼名為 InstallSqlState.sqlUninstallSqlState.sql,與工作階段狀態 NT 服務位在相同的資料夾內。 這些指令碼會建立名為 ASPState 的資料庫和許多預存程序。 不過資料則儲存在 SQL Server 暫時儲存區— TempDB 資料庫內。 這表示若重新啟動 SQL Server 電腦,將會失去工作階段資料。

若要解決此一限制,請使用第二對指令碼。 指令碼的名稱是 InstallPersistSqlState.sqlUninstallPersistSqlState.sql。 在此情況下會建立一個 ASPState 資料庫,不過其資料表會在同一個資料庫中建立,而且是永續性的。 安裝工作階段的 SQL Server 支援時,也會建立一個工作以刪除工作階段狀態資料庫內逾期的工作階段。 該工作名為 ASPState_Job_DeleteExpiredSessions,每分鐘執行一次。 請注意,SQLServerAgent 服務必須處於執行狀態,工作才能正常運作。

不論您選擇何種模式,撰寫工作階段狀態動作程式碼的方式都不會改變。 您處理的一律是 Session 屬性,並如常讀取和寫入值。 任何行為性差異都是在較低的抽象層處理。 狀態序列化可能是各工作階段模式間最重要的差異。

狀態序列化和還原序列化

您使用同處理序 (In-Process) 模式時,物件會以個別類別之作用中執行個體的形式儲存在工作階段狀態中。 不會有實際的序列化和還原序列化,這表示您實際上可以將建立的任何物件 (包括不可序列化物件和 COM 物件) 儲存在 Session 裡並加以存取,而不會有重大的過度耗用情形。 若您選擇跨處理序狀態提供者,情況則會不同。

在跨處理序架構中,工作階段值是要從原生儲存媒體 (外部 AddDomain、資料庫) 複製到處理要求之 AppDomain 的記憶體中。 為完成工作,需要有序列化/還原序列化層,而且它表示的是跨處理序狀態提供者的主要成本之一。 此一事實對程式碼有一大影響—只有可序列化物件能夠儲存在工作階段字典中。

為了執行資料序列化和還原序列化,ASP.NET 會依相關的型別使用兩種方法。 若是基本型別,ASP.NET 會依賴最佳化的內部序列化程式;若是其他型別 (包括物件和使用者定義的類別),ASP.NET 會使用 .NET 二進位格式子。 基本型別有 StringDateTimeBooleanbytechar,再加上所有的數字型別。 針對這些型別會使用特製的序列化程式,這個程式比預設的一般用途 .NET 二進位格式子快。

最佳化序列化程式不會公開提供,也不會記載。 它只是一個二進位讀取/寫入器,並採用簡單卻有效的儲存結構描述。 序列化程式會使用 BinaryWriter 類別寫出一個位元組來代表型別和值。 讀取序列化位元組時,類別會先擷取一個位元組,偵測要讀取的資料型別,然後再借助 BinaryReader 類別上特定型別的 ReadXxx 方法。

請注意,布林 (Boolean) 和數字型別的大小是已知的,但是字串則不一定。 讀取器利用字串在基礎資料流上一律會加上長度前置詞,且一次編碼為七個位元整數的事實,來判斷字串的正確大小。 資料值反而在儲存時,只會寫入構成日期的刻度總數。 因此基於工作階段序列化的目的,日期等於 Int64 型別。

更複雜的物件 (和自訂物件) 是使用 BinaryFormatter 類別序列化 (只要相關的類別都標記為可序列化)。 所有非基本型別都利用相同的型別識別碼識別,並且以基本型別型態儲存在同一個資料流裡。 大致上,序列化造成的效能衝擊約在 15% 到 25% 之間。 不過請注意,這是假設使用基本型別所做的概略估計。 使用的複雜型別愈多,所需的額外工作就愈多。

有效的工作階段資料儲存必須大量使用基本型別。 因此,至少在理論上,使用 3 個工作階段位置分別儲存物件的 3 個字串屬性,似乎比將物件整個序列化來得好。 但是如果要序列化的物件有 100 個屬性時,會出現什麼情形? 應該佔用 100 個位置,或只使用 1 個? 在許多情況下,有個替代方法,最好是將複雜型別轉換為較簡單型別的彙總。 這種方法是以型別轉換子 (Type Converter) 為基礎。 「型別轉換子」是一個輕便的序列化程式,會將型別的主要屬性當成字串集合傳回。 型別轉換子是使用屬性繫結至基底類別的外部類別。 應儲存的屬性和儲存方式由作者決定。 型別轉換子對 ViewState 儲存也很有幫助,並提供比二進位格式子更有效的工作階段儲存方式。

工作階段的存留期

ASP.NET 工作階段管理的一個重點在於,唯有在第一個項目加入記憶體中字典後,工作階段狀態物件才會開始存留。 唯有在執行如下片段的程式碼後,ASP.NET 工作階段才會視為已開始。

Session["MySlot"] = "Some data";

Session 字典一般會包含 Object 型別;若要反向讀取資料,必須將傳回值轉換為更明確的型別。

string data = (string) Session["MySlot"];

網頁儲存資料至 Session 時,值會載入 HttpSessionState 類別所裝載的定做字典類別。 進行中的要求完成後,字典的內容會清除至狀態提供者。 若是因為沒有利用程式將資料放入字典中,以致工作階段狀態是空的,則不會有資料序列化到儲存媒體,而且更重要的是,ASP.NET Cache、SQL Server 或 NT 狀態服務中都不會建立位置以追蹤目前的工作階段。 這麼做是基於效能考量,不過對處理工作階段識別碼的方式有很大的影響: 會為每個要求產生一個新的工作階段識別碼,直到在工作階段字典中儲存一些資料為止。

若是需要附加工作階段狀態至進行中的要求,HTTP 模組會擷取工作階段識別碼 (如果這不是開始要求) 並在設定的狀態提供者中尋找。 要是未傳回資料,HTTP 模組會為要求產生新的工作階段識別碼。 這可以利用以下網頁輕易測試:

<%@ Page Language="C#" Trace="true" %>
</html>
<body>
<form runat="server">
<asp:button runat="server" text="Click" />
</form>
</body>
</html>

只要按下按鈕,且網頁貼回,就會以追蹤資訊文件形式產生新的工作階段識別碼。

[圖 3] 針對應用程式內未在工作階段字典中儲存資料的每個要求,產生一個新的工作階段識別碼

Session_OnStart 事件的情形又是如何呢? 也會針對每個要求引發嗎? 如果應用程式定義了 Session_OnStart 處理常式,則即使工作階段狀態是空的也會加以儲存。 因此,在第一個要求之後,所有要求的工作階段識別碼都會保持不變。 最基本的是,除非絕對必要,否則不使用 Session_OnStart 處理常式。

若是工作階段逾時或放棄,無狀態應用程式的工作階段識別碼不會隨著下次存取變更。 即使工作階段狀態逾期,工作階段識別碼仍會持續到瀏覽器工作階段結束為止。 這表示只要瀏覽器執行個體保持不變,就會使用同一個工作階段識別碼在不同時間代表多個工作階段。

Session_OnEnd 事件會傳送工作階段結束的訊號,用於執行結束工作階段所需的任何清除程式碼。 不過要注意只有InProc 模式才支援事件。也就是,只有工作階段資料儲存在 ASP.NET 背景工作處理序時才支援。 必須先存在工作階段,才會引發 Session_OnEnd,亦即您必須先在工作階段狀態中儲存一些資料,而且至少要完成一個要求。

在 InProc 模式中,會為工作階段狀態 (當成項目加入快取中) 指定一個彈性到期原則。 彈性到期表示若在指定的時間內未使用到項目,就會移除項目。 在這段時間內提供服務的任何要求會重設到期倒數。 工作階段狀態項目的時間間隔會設定為工作階段逾時。 重設工作階段狀態到期所使用的技巧很簡單也很直覺: 工作階段 HTTP 模組只會讀取儲存在 ASP.NET Cache 裡的工作階段狀態項目。 考慮到 ASP.NET Cache 物件的內部結構,這可以估計為更新彈性期間。 因此快取項目到期時,工作階段也就逾時。

逾時的項目會自動從快取移除。 狀態工作階段模組也會指示一個移除回呼函式,這是此項目到期原則的一部份。 快取會自動叫用移除函式,再由函式引發 Session_OnEnd 事件。 如果應用程式是透過跨處理序元件來實作工作階段管理,則永遠不會引發結束事件。

無 Cookie 工作階段

每一個作用中 ASP.NET 工作階段都使用只由 URL 允許的字元構成的 120 位元字串來加以識別。 工作階段識別碼是使用亂數產生器 (RNG) 密碼編譯提供者產生。 服務提供者會傳回一連串隨機產生的 15 個數字 (15 位元組 x 8 位元 = 120 位元)。 然後再將亂數陣列對應至有效的 URL 字元,並以字串形式傳回。

工作階段識別碼字串會傳遞至瀏覽器,然後以下列兩種方式之一傳回伺服器應用程式: 使用 Cookie (如典型的 ASP 中) 或修改過的 URL。 依照預設,工作階段狀態模組會在用戶端上建立一個 HTTP Cookie,但是修改過的 URL 可以在內嵌工作階段識別碼字串的情況下使用,尤其是針對無 Cookie 的瀏覽器。 採用哪一種方法需視應用程式的 web.config 檔案中儲存的組態設定而定。 若要設定工作階段設定,要使用 <sessionState> 區段和 cookieless 屬性。

<sessionState cookieless="true|false" />

依照預設,無 Cookie 屬性為 False,表示會採用 Cookie。 Cookie 只是由網頁放到用戶端硬碟上的一個文字檔。 ASP.NET 裡的 Cookie 是由 HttpCookie 類別的執行個體表示。 Cookie 通常有一個名稱、一組值和一個到期時間。 無 Cookie 屬性設定為 False 時,工作階段狀態模組實際上會建立一個名為 ASP.NET_SessionId 的 Cookie,並將工作階段識別碼儲存在其中。 Cookie 是如下列虛擬程式碼所示般建立而成:

HttpCookie sessionCookie;
sessionCookie = new HttpCookie("ASP.NET_SessionId", sessionID);
sessionCookie.Path = "/";

工作階段 Cookie 的到期時間很短,並且會在每個成功的要求後加以更新。 Cookie 的 Expires 屬性指示 Cookie 在用戶端上到期的當日時數。 若是像工作階段 Cookie 一樣未明確設定,Expires 屬性會預設為 DateTime.MinValue,也就是 .NET Framework 裡允許的最小可能時間單位。

若要停用工作階段 Cooike,需在組態檔裡將無 Cookie 屬性設定為 True,如下所示:

<configuration>
    <system.web>
        <sessionState cookieless="true" />
    </system.web>
</configuration>

現在假設您要求下列 URL 的網頁:

http://www.contoso.com/sample.aspx

瀏覽器網址列裡顯示的實際內容會稍有不同,已經包含了工作階段識別碼,如下所示:

http://www.contoso.com/(5ylg0455mrvws1uz5mmaau45)/sample.aspx

工作階段狀態 HTTP 模組執行個體化後,會檢查無 Cookie 屬性的值。 若為真,會將要求重新導向 (HTTP 302) 至修改過的虛擬 URL,URL 的網頁名稱前會包含工作階段識別碼。 再次處理時,要求會內嵌工作階段識別碼。如果要求啟動新的工作階段,HTTP 模組會產生新的工作階段識別碼,然後重新導向要求。 如果要求是個回傳,則工作階段識別碼早已在其中,因為回傳使用的是相對的 URL。

使用無 Cookie 工作階段的缺點在於叫用絕對 URL 時,會遺失工作階段狀態。 使用 Cookie 時,您可以清除網址列,移至另一個應用程式,然後返回前一個應用程式,擷取相同的工作階段值。 如果是在停用工作階段 Cookie 時這麼做,工作階段資料就會遺失。 例如,以下程式碼會中斷工作階段:

<a runat="server" href="/code/page.aspx">Click</a>

若您必須使用絕對 URL,要依靠一點小技巧,並手動新增工作階段識別碼到 URL。 在 HttpResponse 類別上使用 ApplyAppPathModifier 方法。

<a runat="server" 
    href=<% =Response.ApplyAppPathModifier("/code/page.aspx")%> >Click</a>

ApplyAppPathModifier 方法會取用代表 URL 的字串,並傳回內嵌工作階段資訊的絕對 URL。 例如,在必須從 HTTP 網頁重新導向至 HTTPS 網頁的情況下,這種技巧特別有用。

總結

工作階段狀態原先是由典型的 ASP 提出,是一種字典架構的 API,允許開發人員在工作階段的持續期間內儲存自訂資料。 ASP.NET 裡的工作階段狀態支援兩種主要功能: 無 Cookie 工作階段識別碼的儲存和傳輸,以及工作階段資料實際存放區的狀態提供者。 ASP.NET 為了實作新功能,使用 HTTP 模組管理工作階段狀態至進行中要求之內容的繫結。

典型的 ASP 裡,使用工作階段狀態意味著使用 Cookie。 但是在 ASP.NET 裡已經不是如此了,ASP.NET 可以使用替代的無 Cookie 結構描述。 歸功於 HTTP 模組的動作,任何要求的 URL 都會打散以包含工作階段識別碼,並且重新導向。 下次相同的模組會處理從 URL 擷取工作階段識別碼,並利用它來擷取任何儲存的狀態。

工作階段的實際狀態可以利用三種可能的方式儲存—同處理序記憶體、跨處理序記憶體和 SQL Server 資料表裡。 資料必須經歷序列化/還原序列化處理,才能為應用程式所用。 HTTP 模組會在要求的開始,從提供者複製工作階段值到應用程式的記憶體。 要求完成後,會將修改過的狀態清除回提供者。 這種資料流量會對效能造成不同程度的不利影響,但是可以大幅加強可靠性和穩定性,並使得支援 Web Farm 和 Web 處理序區架構變得極為簡單。

關於作者

Dino Esposito 目前在義大利羅馬擔任教師兼顧問。 Dino 是 Wintellect 團隊的成員,專攻 ASP.NET 和 ADO.NET,大部分時間都在歐洲和美國從事教學和顧問工作。 Dino 還負責 Wintellect 的 ADO.NET 教育軟體,並為 MSDN Magazine Cutting Edge 專欄撰文。