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。否则,将利用一个自定义的数据访问对象(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 COMSession对象的主要目的就是提供对 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.0Join函数将所有的数组元素组合成单个字符串,然后再将其存入会话对象中,从而实现这种功能。利用 Visual Basic 6.0Split函数将该字符串分解成单独的数组元素即可完成反向操作。在 .NET 框架方面,Join和Split方法都是String类的成员。
小结
ASP.NET 代表了一种全新的编程典范和结构,并且比传统的 ASP 具有更多优势。虽然从 ASP 迁移到 ASP.NET 并不是一个简单的过程,但 ASP.NET 更好的编程模型和更高的性能使得这种转换过程物有所值。除了将 COM 对象存储在Session对象中的情况外,本文所述的方法提供了一种解决方案,使得这种迁移过程更加简单。
关于作者
Billy Yuen就职于北加州的 Microsoft 硅谷技术中心。此中心致力于开发 Microsoft .NET 框架解决方案。如果希望与他联系,可发送电子邮件至billyy@microsoft.com。
相关推荐
为了解决这个问题,ASP.NET提供了会话状态功能,使得开发者可以存储和检索用户在不同页面间导航时的数据。 会话状态的实现基于会话ID,这是一个唯一标识符,通过HTTP响应返回给客户端(通常是浏览器),然后在后续...
这就导致了如何在两种技术之间共享会话状态成为了一个需要解决的问题。 ***的一个主要优点是其性能较ASP有显著提升。在***中,Web页面是从System.Web.UI.Page类派生的,该类包含了一个HttpSession对象的实例用于...
特别是在基于ASP.NET的应用程序中,开发者需要掌握如何管理和利用会话状态来提高应用程序的功能性和用户体验。本文将详细介绍在ASP.NET中实现会话状态的基础知识。 #### 什么是会话状态? 会话状态(Session State...
在ASP.NET中,Session是服务器端存储用户特定信息的一种机制,它允许开发者在整个会话期间保持用户状态。然而,当有多个网站或者应用程序需要共享同一用户的Session时,传统的InProc Session模式(将Session数据存储...
使用隐藏框架的方式实现Session共享.绝对的安全, login.asp页面为主页面, 不管是查看页面源文件还是url都不会泄密,很安全的.
比如,ViewState是ASP.NET默认使用的一种状态管理方式,它可以将页面状态在服务器和客户端之间传递。 在标签中提到了“asp.net实例”,这可能意味着源码中包含了一些具体的示例应用,如登录注册系统、购物车、用户...
在ASP.NET中,会话状态是通过一系列组件和服务来管理和实现的。其中最重要的是`SessionStateModule` HTTP模块,它负责处理会话状态相关的任务,比如会话ID的生成、会话数据的检索和绑定等。 #### 会话状态的存储 ...
ASP.NET状态管理是Web开发中的核心概念,它用于在客户端和服务器之间持久化数据,确保用户在浏览网站时保持特定的上下文。本篇将详细探讨四种主要的状态管理技术:Cookies、Session、ViewState和Application。 **...
2. **内置对象**:ASP.NET提供了一系列内置对象,如Request(用于处理用户请求的数据)、Response(用于发送响应到客户端)、Session(用于存储用户会话信息)和Application(用于全局共享数据)。理解这些对象的...
在ASP.NET中,状态管理是开发复杂Web应用的关键技术之一,尤其考虑到HTTP协议的无状态特性,这使得在用户会话期间保持数据变得至关重要。本文将深入探讨ASP.NET中的几种状态管理技术,包括它们的工作原理、优缺点...
1. **视图状态(ViewState)**:ASP.NET提供的默认机制,用于在多次页面请求之间保留页面和控件的状态。它是通过将状态信息编码为字符串并存储在隐藏字段中来实现的。 2. **控件状态(ControlState)**:用于保存控件...
1. **事件处理**:在ASP.NET中,可以定义按钮点击、页面加载等事件的处理函数,这些函数在C#后台代码中实现,负责处理用户操作和状态更新。 2. **状态维护**:可能需要使用`ViewState`、`Session`或`Application`...
- ASP.NET提供了多种状态管理机制,如视图状态(View State)、隐藏字段、会话状态(Session State)、应用程序状态(Application State)等,用于在页面间或用户会话中保持数据。 - 正确选择和使用状态管理方法...
7. **状态管理**:包括Session、Application、Cookie等机制,用于在用户会话间存储和共享数据。 8. **配置管理**:ASP.NET允许开发者通过web.config文件配置应用程序的各个方面,如连接字符串、安全性设置等。 9. ...
4. **Session和Application状态管理**:在ASP.NET中,Session用于存储单个用户的数据,而Application则用于共享所有用户的信息。在线成员显示可能就是通过Application来跟踪当前在线的用户。 5. **私聊实现**:私聊...
ASP.NET的身份验证服务可以轻松实现这一目标,同时还可以使用FormsAuthentication来处理用户的登录状态。 5. **数据展示与分页** GridView控件常用于显示数据表形式的数据,如论坛的帖子列表。项目中可能运用了...
2. Session对象:ASP.NET的Session对象提供了一种在用户会话期间跨多个请求存储和检索数据的方法。只需在源页面上设置Session变量,如`Session["Key"] = "Value"`,然后在目标页面上用`string value = (string)...
ASP.NET状态管理是Web应用程序开发中的关键概念,它允许开发者在用户交互的不同阶段保存和恢复数据,从而提供更丰富的用户体验。在ASP.NET中,状态管理主要包括以下几种方式: 1. **视图状态(ViewState)**:这是ASP...