如何在傳統(tǒng) ASP 和 ASP.NET 之間共享會話狀態(tài)
Billy Yuen
Microsoft Corporation
2003 年 2 月
適用于:
Microsoft ASP.NET
摘要: 討 論如何利用 Microsoft .NET 框架類和 .NET 框架的序列化特性,以便在傳統(tǒng) ASP 和 Microsoft ASP.NET 之間共享會話狀態(tài)。通過共享會話狀態(tài),就允許在并行運行現(xiàn)有的 ASP 應(yīng)用程序和 ASP.NET 應(yīng)用程序的同時,分階段地將 ASP 應(yīng)用程序轉(zhuǎn)換為 ASP.NET 應(yīng)用程序。(12 頁打印頁)
下載本文的源代碼 。
本頁內(nèi)容
簡介
概念綜述
ASP.NET 實現(xiàn)
ASP 實現(xiàn)
演示程序
在現(xiàn)有的 ASP 應(yīng)用程序中嵌入 COM 對象
局限性/改進(jìn)
小結(jié)
簡介
Microsoft ASP.NET 是最新的 Microsoft 技術(shù),用于開發(fā)基于 Web 的應(yīng)用程序。相比傳統(tǒng)的 ASP 腳本技術(shù)而言,它具有很多優(yōu)點,其中包括:1) 將 UI 表示形式從業(yè)務(wù)邏輯中分離出來,從而提供更好的開發(fā)結(jié)構(gòu); 2) 其代碼是完全編譯的,而在傳統(tǒng) ASP 中代碼是解釋的;和 3) 其編譯特性結(jié)合其高速緩存支持,就意味著相對用傳統(tǒng) ASP 編寫的等效站點而言,使用 ASP.NET 編寫的站點的性能有顯著提高。
盡管將現(xiàn)有的 ASP 應(yīng)用程序轉(zhuǎn)換到 ASP.NET 具有潛在的益處,但很多現(xiàn)有的 ASP 應(yīng)用程序都具有關(guān)鍵的使命并且是相當(dāng)復(fù)雜的。這種轉(zhuǎn)換過程可能需要大量資源,并可能給現(xiàn)有的應(yīng)用程序帶來額外的風(fēng)險。要解決這些問題,一種方法就是同時運 行 ASP 和 ASP.NET,并一次只將應(yīng)用程序的一部分轉(zhuǎn)換為 ASP.NET。為了同時運行新的和舊的應(yīng)用程序,就需要一種機制在傳統(tǒng) ASP 和 ASP.NET 之間共享會話狀態(tài)。在本文中,我將討論如何利用 Microsoft.NET 框架的若干類和序列化特性來共享這些會話狀態(tài)。
概念綜述
Cookie 是 Web 應(yīng)用程序用來標(biāo)識用戶會話的最常用方法,可供傳統(tǒng) ASP 和 ASP.NET 二者用來標(biāo)識會話狀態(tài)。而用 ASP 腳本將會話狀態(tài)信息存儲在內(nèi)存中,且不能與其他應(yīng)用程序(如 ASP.NET)共享。如果會話狀態(tài)以一種通用格式存儲在 Microsoft SQL Server 中,則傳統(tǒng)的 ASP 和 ASP.NET 都能訪問會話狀態(tài)。
在此示例中,使用了一個名為 mySession 的 cookie 來標(biāo)識用戶會話。當(dāng)用戶向 Web 應(yīng)用程序發(fā)出請求時,該用戶將被發(fā)放一個唯一的 cookie 以便標(biāo)識該會話。在后續(xù)的請求中,瀏覽器將該唯一的 cookie 發(fā)送回服務(wù)器以標(biāo)識該會話。在加載所請求的 Web 頁面之前,一個自定義的對象將利用該唯一 cookie 從 SQL Server 中重新加載用戶會話數(shù)據(jù)。在 Web 頁面中通過該自定義的對象即可訪問會話狀態(tài)。在 Web 請求結(jié)束后,隨著該請求的終止,會話數(shù)據(jù)將被保存回 SQL Server 中(參見圖 1)。
ASP.NET 實現(xiàn)
在 ASP.NET 中,每個 Web 頁面都是從 System.Web.UI.Page 類派生出來的。 Page 類中包含 HttpSession 對象的一個實例以用于會話數(shù)據(jù)。在本示例中,從 System.Web.UI.Page 派生了一個名為 SessionPage 的自定義 Page 類,以實現(xiàn)與 Page 類完全相同的各種特性。派生頁的唯一不同之處就是利用一個自定義的會話對象重寫了默認(rèn)的 HttpSession 。(利用實例變量的 new 修飾符,C# 允許派生類隱藏基類的成員。)
public class SessionPage : System.Web.UI.Page
{
...
public new mySession Session = null;
...
}
自定義的會話類負(fù)責(zé)利用 HybridDictionary 對象將會話狀態(tài)存儲到內(nèi)存中。( HybridDictionary 能夠高效地處理任何數(shù)量的會話元素。)為了實現(xiàn)與傳統(tǒng) ASP 之間的互操作性,該自定義的會話類將會話數(shù)據(jù)類型限定為僅允許字符串型。(默認(rèn)的 HttpSession 允許將任何類型的數(shù)據(jù)存儲在會話中,而這將不能與傳統(tǒng) 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 類公開不同的事件和方法以供進(jìn)行自定義。特別地, OnInit 方法用于設(shè)置 Page 對象的初始化狀態(tài)。如果該請求不具有 mySession cookie,則將給請求者發(fā)放一個新的 mySession cookie。否則,將利用一個自定義的數(shù)據(jù)訪問對象( SessionPersistence )從 SQL Server 中檢索會話數(shù)據(jù)。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 ,以二進(jìn)制格式對會話狀態(tài)進(jìn)行序列化和反序列化。隨后,可以將所得到的二進(jìn)制會話狀態(tài)數(shù)據(jù)以 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;
}
當(dāng)該請求結(jié)束時,將激發(fā) Page 類的 Unload 事件,注冊用于 Unload 事件的事件處理程序?qū)挃?shù)據(jù)序列化成二進(jìn)制格式,并將所得的二進(jìn)制數(shù)據(jù)保存到 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 類及其相關(guān)類都封裝在 SessionUtility 程序集中。在新的 ASP.NET 項目中,將建立一個對該 SessionUtility 程序集的引用,并且為了與傳統(tǒng) ASP 代碼共享會話,將從 SessionPage 而不是 Page 類派生出每個頁面。一旦完成遷移過程,通過注釋掉 SessionPage 類中的 Session 變量聲明即可解除基類 HttpSession 的隱藏,從而新的應(yīng)用程序可切換回使用本機的 HttpSession 對象。
ASP 實現(xiàn)
本 機的 ASP 會話只能將會話數(shù)據(jù)存儲在內(nèi)存中。為了將會話數(shù)據(jù)存儲到 SQL Server 中,我們編寫了一個自定義的 Microsoft Visual Basic6.0 COM 對象以管理會話狀態(tài),而不使用本機的會話對象進(jìn)行管理。這個 COM 對象將在每個 Web 請求開始時得以實例化,并從 SQL Server 處重新加載會話數(shù)據(jù)。當(dāng) ASP 腳本完成時,此對象將終止,并且會話狀態(tài)將被保存回 SQL Server 中。
Visual Basic 6 COM Session 對象的主要目的就是提供對 Microsoft Internet Information Server 內(nèi)部對象的訪問。Visual Basic 6.0 COM Session 對象使用 SessionUtility 程序集的 mySession 類來保留會話狀態(tài),并使用 SessionUtility 的 SessionPersistence 類從 SQL Server 中加載會話數(shù)據(jù)或?qū)挃?shù)據(jù)保存回 SQL Server。利用 regasm.exe 實用工具, mySession 和 SessionPersistence 類可被公開為 COM 對象。regasm.exe 實用工具能夠注冊并創(chuàng)建一個類庫,以便 COM 客戶端使用各個框架類。
在該對象的構(gòu)造過程中,會話狀態(tài)信息得以重新加載。構(gòu)造函數(shù) ( class_initialize ) 將首先從 Application 對象中檢索會話 cookie、會話超時 ( SessionTimeOut ) 和數(shù)據(jù)庫連接字符串 (SessionDSN),并創(chuàng)建 mySession 類的一個實例以持有這些會話數(shù)據(jù)。然后,構(gòu)造函數(shù)將嘗試?yán)媒o定的 cookie 從 SQL Server 中重新加載會話數(shù)據(jù)。如果 SQL Server 不包含相應(yīng)的會話信息,或者該會話已經(jīng)過期,則將發(fā)放一個新的 cookie。如果 SQL Sever 確實返回會話狀態(tài)數(shù)據(jù),則這些會話狀態(tài)將被存儲在 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
當(dāng)該對象實例超出腳本的作用范圍時,析構(gòu)函數(shù) ( class_terminate ) 將執(zhí)行。析構(gòu)函數(shù)將利用 SessionPersistence.SaveSession() 方法保持會話數(shù)據(jù)。如果這是新會話,析構(gòu)函數(shù)還會向瀏覽器回送一個新 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 會話管理器和演示代碼。
演示程序
本演示程序的設(shè)計目的為遞增并顯示一個數(shù)字。不管加載哪個頁面,該數(shù)字將總是遞增,因為其數(shù)值存儲在 SQL Server 中且在傳統(tǒng) ASP 和 ASP.NET 之間共享。
演示程序的設(shè)置步驟
-
創(chuàng)建一個名為 SessionDemoDb 的新數(shù)據(jù)庫。
-
創(chuàng)建 SessState 表 (osql.exe –E –d SessionDemoDb –i Session.sql)。
-
創(chuàng)建名為 Demo 的新虛擬目錄。
-
關(guān)閉 ASP 配置選項卡中的 ASP Session。
-
將 web.config、testPage.aspx、Global.asa、testPage.asp 和 GlobalInclude.asp 復(fù)制到虛擬目錄中。
-
更新 Global.asa 和 web.config 中的 DSN 字符串設(shè)置。會話超時設(shè)置是可選的。默認(rèn)值為 20 分鐘。聽
-
將 SessionUtility.dll 安裝到 Global Assembly Cache (gacutil /i SessionUtility.dll)。
-
利用 regasm.exe 將 SessionUtility.dll 公開為 COM 對象 (regasm.exe SessionUtility.dll /tlb:SessionUtility.tlb)。
-
將 SessionManager.dll 復(fù)制到一個本地目錄中,并利用 regsvr32.exe 注冊該文件 (regsvr32 SessionManager.dll)。
-
為 IUSR_<machine_name> 帳號賦予對 SessionMgr.dll 的讀和執(zhí)行權(quán)限。
演示程序的運行步驟
-
啟動 Microsoft Internet Explorer。
-
加載傳統(tǒng) ASP 的 testPage.asp。Web 頁面中應(yīng)該顯示數(shù)字 "1"。
-
單擊 Internet Explorer 上的刷新按鈕,重新加載該頁面。該數(shù)字應(yīng)該遞增。
-
將 URL 改為 ASP.NET 版的 testPage.aspx。該數(shù)字應(yīng)該繼續(xù)遞增。
-
如果首先啟動 testPage.aspx 頁面,也可重復(fù)同樣的過程。
在現(xiàn)有的 ASP 應(yīng)用程序中嵌入 COM 對象
在開發(fā) ASP 應(yīng)用程序時,慣例是在每個腳本的開始處包含一個文件以便共享公共代碼和常量。要加入自定義的會話對象,最佳的方法就是在公共的包含文件中添加相應(yīng)的實例化代碼。最后一個步驟就是將對該會話對象的全部引用替換為自定義的會話變量名。
局限性/改進(jìn)
如果現(xiàn)有的 ASP 應(yīng)用程序?qū)⒁粋€ COM 對象存儲在 Session 對象中,則此解決方案并不支持這種情況。在這種情況下,需要一個自定義的封送拆收器來序列化/反序列化各種狀態(tài),以便使用自定義的會話對象。此外,此解決 方案不支持存儲字符串類型數(shù)組。但只需稍加努力,我們就可利用 Microsoft Visual Basic6.0 Join 函數(shù)將所有的數(shù)組元素組合成單個字符串,然后再將其存入會話對象中,從而實現(xiàn)這種功能。利用 Visual Basic 6.0 Split 函數(shù)將該字符串分解成單獨的數(shù)組元素即可完成反向操作。在 .NET 框架方面, Join 和 Split 方法都是 String 類的成員。
小結(jié)
ASP.NET 代表了一種全新的編程典范和結(jié)構(gòu),并且比傳統(tǒng)的 ASP 具有更多優(yōu)勢。雖然從 ASP 遷移到 ASP.NET 并不是一個簡單的過程,但 ASP.NET 更好的編程模型和更高的性能使得這種轉(zhuǎn)換過程物有所值。除了將 COM 對象存儲在 Session 對象中的情況外,本文所述的方法提供了一種解決方案,使得這種遷移過程更加簡單。
關(guān)于作者
Billy Yuen 就職于北加州的 Microsoft 硅谷技術(shù)中心。此中心致力于開發(fā) Microsoft .NET 框架解決方案。如果希望與他聯(lián)系,可發(fā)送電子郵件至 billyy@microsoft.com 。
源于:http://msdn.microsoft.com/zh-cn/library/aa479313.aspx
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

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