次の方法で共有


従来の ASP と ASP.NET でセッション状態を共有する方法

Billy Yuen
Microsoft Corporation

February 2003

対象 :
   Microsoft® ASP.NET

概要 : Microsoft .NET Framework のクラスとシリアル化機能を使用して、従来の ASP と Microsoft ASP.NET とでセッション状態を共有する方法について説明します。セッション状態を共有することで、アプリケーションを並列に実行しながら段階的に既存の ASP アプリケーションを ASP.NET アプリケーションに移行できます。

この記事のソース コードをダウンロードする

目次

はじめに
概念的な概要
ASP.NET の実装
ASP の実装
デモ プログラム
既存の ASP アプリケーションへの COM オブジェクトの組み込み
制限事項と改善点
まとめ

はじめに

Microsoft® ASP.NET は、マイクロソフトの最新の Web ベース アプリケーション開発用テクノロジです。このテクノロジは従来の ASP スクリプト テクノロジと比べて次のような多くの利点があります。 1) UI 表示とビジネス ロジックを分離することで開発構造がより適切になっています。 2) コードが従来の ASP のようにインタプリタで実行されるのではなく、完全にコンパイルされます。 3) キャッシュ サポートと連動したコンパイル機能により、ASP.NET で記述したサイトは従来の ASP で記述した同等のサイトに比べてパフォーマンスが格段に向上します。

既存の ASP アプリケーションから ASP.NET への移行には潜在的な利点があるにもかかわらず、既存の ASP アプリケーションの多くは基幹業務を行っており、複雑なものです。移行の過程ではリソースを集約する必要が出てきたり、既存のアプリケーションの危険性を高めてしまうことがあります。これらの問題を解決する 1 つのアプローチは、ASP と ASP.NET を並列に実行し、ある時点でアプリケーションのあるセッションを ASP.NET に移行することです。新旧のアプリケーションを並列に実行するためのメカニズムは、従来の ASP と ASP.NET でセッション状態を共有する必要があります。この記事では、Microsoft® .NET Framework のいくつかのクラスとシリアル化機能を使用して、セッション状態を共有する方法について説明します。

概念的な概要

Web アプリケーションがユーザー セッションを識別する最も一般的な方法が Cookie であり、従来の 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 から Page クラスと同じ機能をすべて提供するカスタム Page クラス SessionPage を派生します。派生したページで唯一異なる点は、既定の 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 Framework の BinaryFormatter を使用して、パフォーマンスが最高になるように、バイナリ形式でセッション状態のシリアル化およびシリアル化解除を行います。生成されたバイナリ形式のセッション状態データは、SQL Server に image フィールド型として格納できます。

      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 コードとセッションを共有するため、Page クラスではなく SessionPage クラスから派生します。移植が完了後に、新しいアプリケーションでは、基本の HttpSession を公開する SessionPage クラスの Session 変数宣言をコメント アウトして、再びネイティブの HttpSession オブジェクトを使用するように切り替えます。

ASP の実装

ネイティブの ASP セッションは、メモリ内にセッション データを格納することしかできません。SQL Server にセッション データを格納するには、ネイティブのセッション オブジェクトを使用せずに、セッション状態を管理するカスタム Microsoft® Visual Basic® 6.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 に対してセッション データの読み込みと保存を行います。 mySession クラスと SessionPersistence クラスは、 regasm.exe ユーティリティを使用して COM オブジェクトとして公開します。 regasm.exe ユーティリティは、Framework クラスを使用する COM クライアントのタイプ ライブラリを登録したり作成できます。

セッション状態情報は、オブジェクトの構築時に再度読み込まれます。コンストラクタ (class_initialize) は、まずセッションの Cookie、セッションのタイムアウト (SessionTimeOut)、およびデータベース接続文字列 (SessionDSN) を Application オブジェクトから取得します。次に、セッション データを保持する 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

この記事の冒頭にあるリンクをクリックすると、ASP.NET SessionUtility プロジェクト、COM Session Manager、およびデモ コードのソース コードをダウンロードできます。

デモ プログラム

デモ プログラムは数字をインクリメントして表示するのが仕様です。数値は 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_<machine_name> アカウントに SessionMgr.dll に対する読み込みと実行のアクセス権を与えます。

デモ プログラムの実行手順

  1. Microsoft® Internet Explorer を起動します。
  2. 従来の ASP の testPage.asp を読み込みます。Web ページに数字の "1" が表示されます。
  3. Internet Explorer の [更新] をクリックして、ページを再読み込みします。数字がインクリメントされます。
  4. URL を ASP.NET の testPage.aspx に変更します。数字はインクリメントを続けます。
  5. 最初に testPage.aspx ページから始めても同じ処理を繰り返すことができます。

既存の ASP アプリケーションへの COM オブジェクトの組み込み

ASP アプリケーション開発では、共通のコードや定数を共有するために各スクリプトの最初でファイルをインクルードすることがよくあります。カスタム セッション オブジェクトの組み込みに最適な方法は、共通のインクルード ファイルにインスタンスを作成するためのコードを追加することです。最終段階で、セッション オブジェクトへのすべての参照をカスタム セッション変数名に置き換るだけで良くなります。

制限事項と改善点

このソリューションは、Session オブジェクトに COM オブジェクトを格納する既存の ASP アプリケーションには対応していません。このようなソリューションの場合、カスタム マーシャラはカスタム セッション オブジェクトを使用するために、状態をシリアル化およびシリアル化解除する必要があります。さらに、このソリューションは文字列の型配列の格納に対応していません。少し手間をかけて、Microsoft® Visual Basic® 6.0 の Join 関数ですべての配列要素を単一の文字列に連結してからセッション オブジェクトに格納すれば、この機能は実装できます。 Visual Basic 6.0 の Split 関数を使用してこの文字列を個別の配列要素に分割すると、逆のことが可能になります。.NET Framework 上では、Join メソッドと Split メソッドは String クラスのメンバです。

まとめ

新しいプログラミング パラダイムとアーキテクチャを意味する ASP.NET は、従来の ASP と比べると多くの点で優れています。ASP から ASP.NET への移行は単純な作業ではありません。しかし、プログラミング モデルが改良され、ASP.NET のパフォーマンスが改善されるので、それだけの移行作業を行うだけの価値があります。 Session オブジェクトに COM オブジェクトを格納する点を除けば、この記事で紹介したアプローチは移行作業を簡単に進めるソリューションになるでしょう。

著者紹介

Billy Yuen は、北カリフォルニアの Microsoft Technology Center Silicon Valley に勤務しています。ここは、Microsoft .NET Framework ソリューションの開発に特化したセンターです。著者へのメールの宛先は billyy@microsoft.com です。