如何在傳統 ASP 和 ASP.NET 之間共用工作階段狀態
發布日期 : 4/1/2004 | 更新日期 : 4/1/2004
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)。
圖
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。否則,將利用一個自訂的Data Access Objects(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;}elseSession = 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;elseSaveCmd.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 SessionPersistenceSet myObjectContext = GetObjectContext()mySessionID = ReadSessionID()myDSNString = GetConnectionDSN()myTimeOut = GetSessionTimeOut()myIsNewSession = FalseCall InitContentsExit SubErrHandler:Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.DescriptionEnd SubPrivate Sub InitContents()On Error GoTo ErrHandler:Const METHOD_NAME As String = "InitContents"If mySessionID = "" ThenSet myContentsEntity = New mySessionmySessionID = mySessionPersistence.GenerateKeymyIsNewSession = TrueElseSet myContentsEntity =mySessionPersistence.LoadSession(mySessionID, myDSNString, myTimeOut)End IfExit SubErrHandler:Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.DescriptionEnd Sub
當該對象執行個體超出指令碼的作用範圍時,解構函式 (class_terminate) 將執行。解構函式將利用 SessionPersistence.SaveSession() 方法保持會話資料。如果這是新會話,解構函式還會向瀏覽器回送一個新 cookie。
Private Sub Class_Terminate()On Error GoTo ErrHandler:Const METHOD_NAME As String = "Class_Terminate"Call SetDataForSessionIDExit SubErrHandler:Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.Description>End SubPrivate 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 = NothingSet myObjectContext = NothingSet mySessionPersistence = NothingExit SubErrHandler:Err.Raise Err.Number, METHOD_NAME & ":" & Err.Source, Err.DescriptionEnd 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。
轉入原英文頁面
返回頁首