如何在经典 ASP 和 ASP.NET 之间共享会话状态

 

比利·袁
Microsoft Corporation

2003 年 2 月

适用于:
   Microsoft® ASP.NET

总结:讨论如何使用 Microsoft .NET Framework 类和 .NET Framework 的序列化功能在经典 ASP 和 Microsoft ASP.NET 之间共享会话状态。 共享会话状态允许在并行运行应用程序时分阶段将现有 ASP 应用程序转换为 ASP.NET 应用程序。 ) (12 个打印页

简介
概念概述
ASP.NET 实现
ASP 实现
演示计划
将 COM 对象合并到现有 ASP 应用程序中
限制/改进
结论

简介

Microsoft® ASP.NET 是用于开发基于 Web 的应用程序的最新 Microsoft 技术。 它比经典 ASP 脚本技术具有许多优势,包括:1) 更好的开发结构,将 UI 呈现与业务逻辑分开;2) 其代码已完全编译,而不是在经典 ASP 中解释为 ;和 3) 其编译功能与其缓存支持相结合意味着使用 ASP.NET 编写的站点的性能明显优于使用经典 ASP 编写的等效站点。

尽管将现有 ASP 应用程序转换为 ASP.NET 的潜在好处,但许多现有 ASP 应用程序的任务关键且很复杂。 转换过程可能占用大量资源,并给现有应用程序带来额外的风险。 解决这些问题的一种方法是并行运行 ASP 和 ASP.NET,一次将应用程序的一个部分转换为 ASP.NET。 若要并行运行新旧应用程序,需要一种机制来在经典 ASP 和 ASP.NET 之间共享会话状态。 在本文中,我将讨论如何使用多个类以及 Microsoft® .NET Framework的序列化功能来共享会话状态。

概念概述

Cookie 是 Web 应用程序标识用户会话的最常见方式,可用于标识经典 ASP 和 ASP.NET 的会话状态。 会话状态信息存储在 ASP 脚本的内存中,不能与其他应用程序(如 ASP.NET)共享。 如果会话状态以通用格式存储在 Microsoft® SQL Server 中,则经典 ASP 和 ASP.NET 都可以访问会话状态。

在此示例中,名为 mySession 的 Cookie 用于标识用户会话。 当用户向 Web 应用程序发出请求时,将向用户颁发唯一 Cookie 来标识会话。 在后续请求中,浏览器会将唯一 Cookie 发送回服务器以标识会话。 在加载请求的网页之前,自定义对象将使用唯一 Cookie 从SQL Server重新加载用户会话数据。 会话状态可通过自定义对象在网页中访问。 Web 请求完成后,会话数据将保留回SQL Server,因为请求终止 (见图 1) 。

Aa479313.converttoaspnet_fig1 (en-us,MSDN.10) .gif

图 1. 示例数据流

ASP.NET 实现

在 ASP.NET 中,每个网页都派生自 System.Web.UI.Page 类。 Page 类聚合会话数据的 HttpSession 对象的实例。 在此示例中,名为 SessionPage 的自定义 Page 类派生自 System.Web.UI.Page,以提供与 Page 类相同的所有功能。 与派生页的唯一区别是默认 HttpSession 会用自定义会话对象重写。 (使用实例变量 的新 修饰符,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 Framework的 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;
      }

在请求结束时,将触发 PageUnload 事件,并且向 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 程序集进行引用,并且每个页面都将派生自 SessionPage 而不是 Page 类,以便与经典 ASP 代码共享会话。 移植完成后,新应用程序可以通过注释掉 SessionPage 类中的 Session 变量声明来取消隐藏基本 HttpSession,切换回使用本机 HttpSession 对象。

ASP 实现

本机 ASP 会话只能将会话数据存储在内存中。 为了将会话数据存储到SQL Server,将编写自定义 Microsoft® Visual Basic® 6.0 COM 对象来管理会话状态,而不是使用本机会话对象。 此 COM 对象将在每个 Web 请求的开头实例化,并从SQL Server重新加载会话数据。 ASP 脚本完成后,此对象将终止,会话状态将保留回SQL Server。

Visual Basic 6 COM 会话 对象的主要用途是提供对 Microsoft® Internet Information Server 内部对象的访问。 Visual Basic 6.0 COM Session 对象使用 SessionUtility 程序集的 mySession 类来保存会话状态,使用 SessionUtility 的 SessionPersistence 类使用SQL Server来加载和保存会话数据。 mySessionSessionPersistence 类使用 regasm.exe 实用工具作为 COM 对象公开。 regasm.exe实用工具可以注册并创建一个类型库,以便 COM 客户端使用框架类。

在构造 对象期间,将重新加载会话状态信息。 构造函数 (class_initialize) 将首先从 Application 对象检索会话 cookie、会话超时 (SessionTimeOut) 和数据库连接字符串 (SessionDSN) ,并创建类 mySession 的实例来保存会话数据。 然后,构造函数将尝试使用给定 cookie 从SQL Server重新加载会话数据。 如果SQL Server没有会话信息,或者会话已过期,则会发出新的 Cookie。 如果 SQL 服务器返回会话状态数据,会话状态将存储在 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 之间共享。

设置演示程序的步骤

  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 (regasm.exe SessionUtility.dll /tlb:SessionUtility.tlb) 将SessionUtility.dll公开为 COM 对象。
  9. 将SessionManager.dll复制到本地目录,并使用 regsvr32.exe 将其注册 (regsvr32 SessionManager.dll) 。
  10. 向IUSR_<machine_name> 帐户授予对SessionMgr.dll的读取和执行访问权限。

运行演示程序的步骤

  1. 启动 Microsoft® Internet Explorer。
  2. 加载经典 ASP 的 testPage.asp。 数字“1”应显示在网页中。
  3. 在 Internet Explorer 上单击“刷新”以重新加载页面。 数字应递增。
  4. 将 url 更改为 testPage.aspx for ASP.NET。 该数字应保持递增。
  5. 可以通过首先启动 testPage.aspx 页来重复相同的过程。

将 COM 对象合并到现有 ASP 应用程序中

开发 ASP 应用程序的常见做法是在每个脚本的开头包含一个文件,以共享通用代码和常量。 合并自定义会话对象的最佳方法是在通用包含文件中添加实例化代码。 最后一步只是将对会话对象的所有引用替换为自定义会话变量名称。

限制/改进

此解决方案不支持将 COM 对象存储在 Session 对象中的现有 ASP 应用程序。 在这种情况下,需要使用自定义封送处理程序来序列化/反序列化状态,以便使用自定义会话对象。 此外,此解决方案不支持存储字符串的类型数组。 在将数组元素存储到会话对象之前,可以通过使用 Microsoft® Visual Basic® 6.0 Join 函数将所有数组元素合并为单个字符串来实现此功能。 相反,可以使用 Visual Basic 6.0 Split 函数将字符串拆分回单个数组元素。 在.NET Framework端,JoinSplit 方法是 String 类的成员。

结论

ASP.NET 代表了一种新的编程范例和体系结构,与经典 ASP 一起提供了许多优势。 尽管从 ASP 移植到 ASP.NET 不是一个简单的过程,但更好的编程模型和改进 ASP.NET 的性能将使转换过程变得值得。 除了在 Session 对象中存储 COM 对象外,本文中所述的方法提供了一种解决方案,可简化迁移过程。

关于作者

比利·袁 在北加州的微软技术中心硅谷工作。 本中心重点介绍 Microsoft .NET Framework 解决方案的开发。 可在 联系 billyy@microsoft.com他。

© Microsoft Corporation. 保留所有权利。