asp.net
[前言:]ASP.NET是微軟提供的最新的開發基於Web的應用程式的技術。它提供了大量的比傳統ASP指令碼技術的好處,包括:
1)通過把UI表現層(presentation)與商業邏輯(business logic)分開建立了更好的開發結構;
2)使用完全編譯的代碼代替了傳統ASP的代碼翻譯;
3)它編譯特性與每個支援的方法協同,這意味著使用ASP.NET的網站比使用傳統的ASP網站的效能更高。
儘管把存在的ASP應用程式轉換到ASP.NET有很多潛在的好處,也有些ASP應用程式任務很重要並且複雜。轉換過程可能需要更多資源並給應用程式帶來更多風險。解決這些問題的途徑之一是同時運行ASP和ASP.NET應用程式,在一個時刻將一個對話轉換為ASP.NET。為了同時運行新舊程式,需要建立一個機制在傳統的ASP與ASP.NET間共用對話狀態。本文討論的是怎樣使用.NET架構組件的類和序列化特性共用狀態資訊。
概述
Cookie是Web應用程式識別使用者對話的最常用的方法,可以用於識別傳統的ASP和ASP.NET對話狀態。在ASP指令碼中狀態資訊儲存在記憶體中,不能與其它應用程式(例如ASP.NET)共用。如果對話狀態使用通用格式儲存在微軟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對象的一個執行個體用於處理對話資料。在本例中,叫做SessionPage的自訂Page類來衍生自System.Web.UI.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 ObjectsSessionPersistence從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類使用微軟.NET架構組件的BinaryFormatter來序列化和並行化對話狀態為二進位格式以提供最佳效能。結果的二進位對話資料接著作為圖象欄位類型被存入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類中定義的對話變數切換回使用原來的HttpSession對象來顯示基本的HttpSession。ASP實現
原來的ASP對話只能將對話資料儲存在記憶體中。為了將對話資料儲存到SQL Server,需要寫一個自訂的Visual Basic 6.0 COM對象代替現在的對話對象來管理對話狀態。該COM對象在每個Web請求開始時被初始化,並從SQL Server重新載入對話資料。ASP指令碼完成時,該對象將終止並把對話狀態將返回到SQL Server。
Visual Basic 6 COM Session對象的主要目的是提供對微軟Internet資訊服務器(IIS)內部對象的訪問。Visual Basic 6 COM對話對象使用SessionUtility組件的mySession類來儲存對話狀態,SessionUtility的SessionPersistence類用於載入和儲存對話資料到SQL Server。使用regasm.exe工具將mySession和 SessionPersistence類作為COM對象暴露。regasm.exe工具可以註冊並為COM用戶端建立一個類型庫來使用架構組件類。
在對象的建構函式中狀態資訊被重新載入。建構函式(class_initialize)首先從Application對象中檢索對話cookie、對話逾時設定(SessionTimeOut)、資料庫連接字串(SessionDSN),並建立mySession類的一個執行個體來保持對話資料。接著建構函式將試圖重新使用給定的cookie從SQL Server中載入對話資料。如果SQL Server中沒有對話資訊,或者對話已經終止,將產生一個新的cookie。如果SQL Server返回了對話狀態資料,對話狀態資訊將儲存在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
常式
常式設計為增加並顯示一個數字。不管載入了哪個頁面,由於數字值儲存在SQL Server中並在ASP和ASP.NET間共用,數字將不斷增加。
建立常式的步驟
1. 建立一個新資料庫SessionDemoDb。
2. 建立新表SessState(osql.exe -E -d SessionDemoDb -i Session.sql)。
3. 建立新虛擬目錄Demo。
4. 在ASP配置頁中關閉ASP對話。
5. 將web.config、testPage.aspx、Global.asa、testPage.asp和GlobalInclude.asp複製到虛擬目錄。
6. 更新Global.asa和web.config中的DSN字串設定。對話逾時設定是可選的,預設為20分鐘。
7. 將SessionUtility.dll安裝到全域群組件緩衝(gacutil /i SessionUtility.dll)。
8. 使用regasm.exe把SessionUtility.dll作為COM對象暴露(regasm.exe SessionUtility.dll /tlb:SessionUtility.tlb)。
9. 將SessionManager.dll複製到本地目錄並使用regsvr32.exe註冊(regsvr32 SessionManager.dll)。
10. 賦予IUSR_<電腦名稱>帳戶讀取和運行訪問SessionMgr.dll的許可權。
運行常式的步驟
1. 開啟微軟Internet Explorer。
2. 為傳統的ASP中載入testPage.asp。數字"1"將出現在Web頁中。
3. 點擊Internet Explorer的重新整理來重新載入該頁面。數字將增加。
4. 為ASP.NET改變URL為testPage.aspx。數字仍然增加。
5. 如果使用testPage.aspx開始,過程相同。
在存在的ASP應用程式中插入COM對象
開發ASP應用程式的一個通常的習慣是在每個指令碼開始時包含一個檔案來共用代碼和常量。插入自訂對話對象的最後途徑是在公有包含檔案中加入範例程式碼。最後一步簡單地使用自訂對話變數名代替了所有到對話對象的引用。
限制/改進
本方案不支援在Session對象中儲存了COM對象的ASP應用程式。在這種情況下,為了使用自訂對話對象,需要一個自訂的調度器來序列化/並行化狀態。此外,本方案也不支援儲存字串類型數組。做一些附加工作可以實現該特性:使用Visual Basic 6.0的Join函數在儲存到對話對象前將數組元素串連成一個字串,反向操作可以使用Visual Basic 6.0的Split函數將字串分解為獨立的數組元素。在.NET架構組件中,Join和Split方法使String類的成員。
結論
ASP.NET提出了新的編程範例和體繫結構,並提供了許多比傳統ASP的好處。儘管將ASP移植到ASP.NET的過程不簡單,ASP.NET更好的編程模型和更高的效能將使轉換過程值得。除了在Session對象中儲存COM對象的特例,本文討論的途徑提供了一個簡單實現遷移的解決方案。