如何在傳統 ASP 和 ASP.NET 之間共享會話狀態
Billy Yuen
Microsoft Corporation
2003 年 2 月
適用于:
Microsoft ASP.NET
摘要: 討 論如何利用 Microsoft .NET 框架類和 .NET 框架的序列化特性,以便在傳統 ASP 和 Microsoft ASP.NET 之間共享會話狀態。通過共享會話狀態,就允許在并行運行現有的 ASP 應用程序和 ASP.NET 應用程序的同時,分階段地將 ASP 應用程序轉換為 ASP.NET 應用程序。(12 頁打印頁)
下載本文的源代碼 。
本頁內容
簡介
概念綜述
ASP.NET 實現
ASP 實現
演示程序
在現有的 ASP 應用程序中嵌入 COM 對象
局限性/改進
小結
簡介
Microsoft ASP.NET 是最新的 Microsoft 技術,用于開發基于 Web 的應用程序。相比傳統的 ASP 腳本技術而言,它具有很多優點,其中包括:1) 將 UI 表示形式從業務邏輯中分離出來,從而提供更好的開發結構; 2) 其代碼是完全編譯的,而在傳統 ASP 中代碼是解釋的;和 3) 其編譯特性結合其高速緩存支持,就意味著相對用傳統 ASP 編寫的等效站點而言,使用 ASP.NET 編寫的站點的性能有顯著提高。
盡管將現有的 ASP 應用程序轉換到 ASP.NET 具有潛在的益處,但很多現有的 ASP 應用程序都具有關鍵的使命并且是相當復雜的。這種轉換過程可能需要大量資源,并可能給現有的應用程序帶來額外的風險。要解決這些問題,一種方法就是同時運 行 ASP 和 ASP.NET,并一次只將應用程序的一部分轉換為 ASP.NET。為了同時運行新的和舊的應用程序,就需要一種機制在傳統 ASP 和 ASP.NET 之間共享會話狀態。在本文中,我將討論如何利用 Microsoft.NET 框架的若干類和序列化特性來共享這些會話狀態。
概念綜述
Cookie 是 Web 應用程序用來標識用戶會話的最常用方法,可供傳統 ASP 和 ASP.NET 二者用來標識會話狀態。而用 ASP 腳本將會話狀態信息存儲在內存中,且不能與其他應用程序(如 ASP.NET)共享。如果會話狀態以一種通用格式存儲在 Microsoft SQL Server 中,則傳統的 ASP 和 ASP.NET 都能訪問會話狀態。
在此示例中,使用了一個名為 mySession 的 cookie 來標識用戶會話。當用戶向 Web 應用程序發出請求時,該用戶將被發放一個唯一的 cookie 以便標識該會話。在后續的請求中,瀏覽器將該唯一的 cookie 發送回服務器以標識該會話。在加載所請求的 Web 頁面之前,一個自定義的對象將利用該唯一 cookie 從 SQL Server 中重新加載用戶會話數據。在 Web 頁面中通過該自定義的對象即可訪問會話狀態。在 Web 請求結束后,隨著該請求的終止,會話數據將被保存回 SQL Server 中(參見圖 1)。
ASP.NET 實現
在 ASP.NET 中,每個 Web 頁面都是從 System.Web.UI.Page 類派生出來的。 Page 類中包含 HttpSession 對象的一個實例以用于會話數據。在本示例中,從 System.Web.UI.Page 派生了一個名為 SessionPage 的自定義 Page 類,以實現與 Page 類完全相同的各種特性。派生頁的唯一不同之處就是利用一個自定義的會話對象重寫了默認的 HttpSession 。(利用實例變量的 new 修飾符,C# 允許派生類隱藏基類的成員。)
public class SessionPage : System.Web.UI.Page
{
...
public new mySession Session = null;
...
}
自定義的會話類負責利用 HybridDictionary 對象將會話狀態存儲到內存中。( HybridDictionary 能夠高效地處理任何數量的會話元素。)為了實現與傳統 ASP 之間的互操作性,該自定義的會話類將會話數據類型限定為僅允許字符串型。(默認的 HttpSession 允許將任何類型的數據存儲在會話中,而這將不能與傳統 ASP 互操作。)
[Serializable]
public class mySession
{
private HybridDictionary dic = new HybridDictionary();
public mySession()
{
}
public string this [string name]
{
get
{
return (string)dic[name.ToLower()];
}
set
{
dic[name.ToLower()] = value;
}
}
}
Page 類公開不同的事件和方法以供進行自定義。特別地, OnInit 方法用于設置 Page 對象的初始化狀態。如果該請求不具有 mySession cookie,則將給請求者發放一個新的 mySession cookie。否則,將利用一個自定義的數據訪問對象( SessionPersistence )從 SQL Server 中檢索會話數據。dsn 和 SessionExpiration 值是從 web.config 中檢索的。
override protected void OnInit(EventArgs e)
{
InitializeComponent();
base.OnInit(e);
}
private void InitializeComponent()
{
cookie = this.Request.Cookies[sessionPersistence.SessionID];
if (cookie == null)
{
Session = new mySession();
CreateNewSessionCookie();
IsNewSession = true;
}
else
Session = sessionPersistence.LoadSession(
Server.UrlDecode(cookie.Value).ToLower().Trim(),
dsn,
SessionExpiration
);
this.Unload += new EventHandler(this.PersistSession);
}
private void CreateNewSessionCookie()
{
cookie = new HttpCookie(sessionPersistence.SessionID,
sessionPersistence.GenerateKey());
this.Response.Cookies.Add(cookie);
}
為了獲得最佳性能, SessionPersistence 類利用 Microsoft .NET 框架的 BinaryFormatter ,以二進制格式對會話狀態進行序列化和反序列化。隨后,可以將所得到的二進制會話狀態數據以 art 字段類型存儲在 SQL Server 中。
public mySession LoadSession(string key, string dsn,
int SessionExpiration)
{
SqlConnection conn = new SqlConnection(dsn);
SqlCommand LoadCmd = new SqlCommand();
LoadCmd.CommandText = command;
LoadCmd.Connection = conn;
SqlDataReader reader = null;
mySession Session = null;
try
{
LoadCmd.Parameters.Add("@ID", new Guid(key));
conn.Open();
reader = LoadCmd.ExecuteReader();
if (reader.Read())
{
DateTime LastAccessed =
reader.GetDateTime(1).AddMinutes(SessionExpiration);
if (LastAccessed >= DateTime.Now)
Session = Deserialize((Byte[])reader["Data"]);
}
}
finally
{
if (reader != null)
reader.Close();
if (conn != null)
conn.Close();
}
return Session;
}
private mySession Deserialize(Byte[] state)
{
if (state == null) return null;
mySession Session = null;
Stream stream = null;
try
{
stream = new MemoryStream();
stream.Write(state, 0, state.Length);
stream.Position = 0;
IFormatter formatter = new BinaryFormatter();
Session = (mySession)formatter.Deserialize(stream);
}
finally
{
if (stream != null)
stream.Close();
}
return Session;
}
當該請求結束時,將激發 Page 類的 Unload 事件,注冊用于 Unload 事件的事件處理程序將會話數據序列化成二進制格式,并將所得的二進制數據保存到 SQL Server 中。
private void PersistSession(Object obj, System.EventArgs arg)
{ sessionPersistence.SaveSession(
Server.UrlDecode(cookie.Value).ToLower().Trim(),
dsn, Session, IsNewSession);
}
public void SaveSession(string key, string dsn,
mySession Session, bool IsNewSession)
{
SqlConnection conn = new SqlConnection(dsn);
SqlCommand SaveCmd = new SqlCommand();
SaveCmd.Connection = conn;
try
{
if (IsNewSession)
SaveCmd.CommandText = InsertStatement;
else
SaveCmd.CommandText = UpdateStatement;
SaveCmd.Parameters.Add("@ID", new Guid(key));
SaveCmd.Parameters.Add("@Data", Serialize(Session));
SaveCmd.Parameters.Add("@LastAccessed", DateTime.Now.ToString());
conn.Open();
SaveCmd.ExecuteNonQuery();
}
finally
{
if (conn != null)
conn.Close();
}
}
private Byte[] Serialize(mySession Session)
{
if (Session == null) return null;
Stream stream = null;
Byte[] state = null;
try
{
IFormatter formatter = new BinaryFormatter();
stream = new MemoryStream();
formatter.Serialize(stream, Session);
state = new Byte[stream.Length];
stream.Position = 0;
stream.Read(state, 0, (int)stream.Length);
stream.Close();
}
finally
{
if (stream != null)
stream.Close();
}
return state;
}
SessionPage 類及其相關類都封裝在 SessionUtility 程序集中。在新的 ASP.NET 項目中,將建立一個對該 SessionUtility 程序集的引用,并且為了與傳統 ASP 代碼共享會話,將從 SessionPage 而不是 Page 類派生出每個頁面。一旦完成遷移過程,通過注釋掉 SessionPage 類中的 Session 變量聲明即可解除基類 HttpSession 的隱藏,從而新的應用程序可切換回使用本機的 HttpSession 對象。
ASP 實現
本 機的 ASP 會話只能將會話數據存儲在內存中。為了將會話數據存儲到 SQL Server 中,我們編寫了一個自定義的 Microsoft Visual Basic6.0 COM 對象以管理會話狀態,而不使用本機的會話對象進行管理。這個 COM 對象將在每個 Web 請求開始時得以實例化,并從 SQL Server 處重新加載會話數據。當 ASP 腳本完成時,此對象將終止,并且會話狀態將被保存回 SQL Server 中。
Visual Basic 6 COM Session 對象的主要目的就是提供對 Microsoft Internet Information Server 內部對象的訪問。Visual Basic 6.0 COM Session 對象使用 SessionUtility 程序集的 mySession 類來保留會話狀態,并使用 SessionUtility 的 SessionPersistence 類從 SQL Server 中加載會話數據或將會話數據保存回 SQL Server。利用 regasm.exe 實用工具, mySession 和 SessionPersistence 類可被公開為 COM 對象。regasm.exe 實用工具能夠注冊并創建一個類庫,以便 COM 客戶端使用各個框架類。
在該對象的構造過程中,會話狀態信息得以重新加載。構造函數 ( class_initialize ) 將首先從 Application 對象中檢索會話 cookie、會話超時 ( SessionTimeOut ) 和數據庫連接字符串 (SessionDSN),并創建 mySession 類的一個實例以持有這些會話數據。然后,構造函數將嘗試利用給定的 cookie 從 SQL Server 中重新加載會話數據。如果 SQL Server 不包含相應的會話信息,或者該會話已經過期,則將發放一個新的 cookie。如果 SQL Sever 確實返回會話狀態數據,則這些會話狀態將被存儲在 mySession 對象中。
Private Sub Class_Initialize()
On Error GoTo ErrHandler:
Const METHOD_NAME As String = "Class_Initialize"
Set mySessionPersistence = New SessionPersistence
Set myObjectContext = GetObjectContext()
mySessionID = ReadSessionID()
myDSNString = GetConnectionDSN()
myTimeOut = GetSessionTimeOut()
myIsNewSession = False
Call InitContents
Exit Sub
ErrHandler:
Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.Description
End Sub
Private Sub InitContents()
On Error GoTo ErrHandler:
Const METHOD_NAME As String = "InitContents"
If mySessionID = "" Then
Set myContentsEntity = New mySession
mySessionID = mySessionPersistence.GenerateKey
myIsNewSession = True
Else
Set myContentsEntity =
mySessionPersistence.LoadSession(mySessionID, myDSNString, myTimeOut)
End If
Exit Sub
ErrHandler:
Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.Description
End Sub
當該對象實例超出腳本的作用范圍時,析構函數 ( class_terminate ) 將執行。析構函數將利用 SessionPersistence.SaveSession() 方法保持會話數據。如果這是新會話,析構函數還會向瀏覽器回送一個新 cookie。
Private Sub Class_Terminate()
On Error GoTo ErrHandler:
Const METHOD_NAME As String = "Class_Terminate"
Call SetDataForSessionID
Exit Sub
ErrHandler:
Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.Description>
End Sub
Private Sub SetDataForSessionID()
On Error GoTo ErrHandler:
Const METHOD_NAME As String = "SetDataForSessionID"
Call mySessionPersistence.SaveSession(mySessionID,
myDSNString, myContentsEntity, myIsNewSession)
If myIsNewSession Then Call WriteSessionID(mySessionID)
Set myContentsEntity = Nothing
Set myObjectContext = Nothing
Set mySessionPersistence = Nothing
Exit Sub
ErrHandler:
Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.Description
End Sub
單擊本文頂部的鏈接,您可以下載 ASP.NET SessionUtility 項目的源代碼 — COM 會話管理器和演示代碼。
演示程序
本演示程序的設計目的為遞增并顯示一個數字。不管加載哪個頁面,該數字將總是遞增,因為其數值存儲在 SQL Server 中且在傳統 ASP 和 ASP.NET 之間共享。
演示程序的設置步驟
-
創建一個名為 SessionDemoDb 的新數據庫。
-
創建 SessState 表 (osql.exe –E –d SessionDemoDb –i Session.sql)。
-
創建名為 Demo 的新虛擬目錄。
-
關閉 ASP 配置選項卡中的 ASP Session。
-
將 web.config、testPage.aspx、Global.asa、testPage.asp 和 GlobalInclude.asp 復制到虛擬目錄中。
-
更新 Global.asa 和 web.config 中的 DSN 字符串設置。會話超時設置是可選的。默認值為 20 分鐘。聽
-
將 SessionUtility.dll 安裝到 Global Assembly Cache (gacutil /i SessionUtility.dll)。
-
利用 regasm.exe 將 SessionUtility.dll 公開為 COM 對象 (regasm.exe SessionUtility.dll /tlb:SessionUtility.tlb)。
-
將 SessionManager.dll 復制到一個本地目錄中,并利用 regsvr32.exe 注冊該文件 (regsvr32 SessionManager.dll)。
-
為 IUSR_<machine_name> 帳號賦予對 SessionMgr.dll 的讀和執行權限。
演示程序的運行步驟
-
啟動 Microsoft Internet Explorer。
-
加載傳統 ASP 的 testPage.asp。Web 頁面中應該顯示數字 "1"。
-
單擊 Internet Explorer 上的刷新按鈕,重新加載該頁面。該數字應該遞增。
-
將 URL 改為 ASP.NET 版的 testPage.aspx。該數字應該繼續遞增。
-
如果首先啟動 testPage.aspx 頁面,也可重復同樣的過程。
在現有的 ASP 應用程序中嵌入 COM 對象
在開發 ASP 應用程序時,慣例是在每個腳本的開始處包含一個文件以便共享公共代碼和常量。要加入自定義的會話對象,最佳的方法就是在公共的包含文件中添加相應的實例化代碼。最后一個步驟就是將對該會話對象的全部引用替換為自定義的會話變量名。
局限性/改進
如果現有的 ASP 應用程序將一個 COM 對象存儲在 Session 對象中,則此解決方案并不支持這種情況。在這種情況下,需要一個自定義的封送拆收器來序列化/反序列化各種狀態,以便使用自定義的會話對象。此外,此解決 方案不支持存儲字符串類型數組。但只需稍加努力,我們就可利用 Microsoft Visual Basic6.0 Join 函數將所有的數組元素組合成單個字符串,然后再將其存入會話對象中,從而實現這種功能。利用 Visual Basic 6.0 Split 函數將該字符串分解成單獨的數組元素即可完成反向操作。在 .NET 框架方面, Join 和 Split 方法都是 String 類的成員。
小結
ASP.NET 代表了一種全新的編程典范和結構,并且比傳統的 ASP 具有更多優勢。雖然從 ASP 遷移到 ASP.NET 并不是一個簡單的過程,但 ASP.NET 更好的編程模型和更高的性能使得這種轉換過程物有所值。除了將 COM 對象存儲在 Session 對象中的情況外,本文所述的方法提供了一種解決方案,使得這種遷移過程更加簡單。
關于作者
Billy Yuen 就職于北加州的 Microsoft 硅谷技術中心。此中心致力于開發 Microsoft .NET 框架解決方案。如果希望與他聯系,可發送電子郵件至 billyy@microsoft.com 。
源于:http://msdn.microsoft.com/zh-cn/library/aa479313.aspx
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
