ASP.NET 2.0 でのキャッシュの改良

By G. Andrew Duthie
Graymad Enterprises, Inc.

February 2004

適用対象 :
    Microsoft(R) ASP.NET

概要 : ASP.NET 2.0 のキャッシュの新機能について説明します。ASP.NET アプリケーション開発者はこれらの機能を使用することにより、次世代の ASP.NET アプリケーションのパフォーマンスを大幅に向上させることができます。

この記事のサンプル コードのダウンロード

目次

はじめに
拡張可能なキャッシュ依存関係
SQL キャッシュの無効化
キャッシュ後の置換
まとめ
参考書籍

はじめに

ASP.NET の機能の中で最も強力かつ便利な (そしておそらく最も見過ごされている) 機能の 1 つが、ページ出力と任意のデータの両方のキャッシュに対する豊富なサポートです。ASP.NET では、レンダリング後のページ出力やユーザー コントロールの出力に加え、コレクションからデータセットまであらゆるデータをメモリに格納できます。また、キャッシュを更新するタイミングについても、時間に基づく単純な有効期限からキーやファイルをベースとするキャッシュの依存関係まで、さまざまな方法で指定できます。

ASP.NET の現行リリースのキャッシュ機能を使用した経験がある方なら、パフォーマンスやスケーラビリティがどれほど向上するかよくご存じのことでしょう。コンテンツをデータベースから取得したりページを再度処理したりすることなくメモリから返すことができるため、パフォーマンスが向上します。また、ページが要求されるたびにプロセッサやファイル システム (およびデータベース) に影響が及ぶことがなくなるため、スケーラビリティが向上します。

しかし、ASP.NET チームは ASP.NET のキャッシュ機能の現状に満足してはいませんでした。そして今回、ASP.NET 2.0 において、キャッシュの便利な新機能がいくつか導入されることになりました。ここでは、これらの新機能を紹介します。ここで取り上げる機能は以下の 3 つです。

  • 拡張可能なキャッシュ依存関係
  • SQL キャッシュの無効化
  • キャッシュ後の置換

拡張可能なキャッシュ依存関係

ASP.NET 1.1 では、依存関係を使用してキャッシュ エントリを自動的に期限切れにすることができますが、この機能はやや制限されたものとなっています。ファイルやその他のキャッシュ キーを依存関係項目として使用できますが、それ以上のことはできません。確かに便利な機能ですが、依存関係のメカニズムを自分で拡張できればと考えていた開発者も多かったのではないでしょうか。今回、新しいバージョンの CacheDependency クラスが導入されたことによって、それが可能になりました。Sealed ではなくなったため、このクラスを継承できるようになったのです。この新しいバージョンでは次の 3 つの新しいメンバが公開されており、開発者はこれらを使用できます。

  • GetUniqueID ? これをオーバーライドすることによって、カスタム キャッシュ依存関係の一意の ID を呼び出し元に返すことができます。
  • DependencyDispose ? カスタム キャッシュ依存関係クラスによって使用されているリソースを破棄するために使用します。カスタム キャッシュ依存関係を作成する際には、このメソッドを実装する必要があります。
  • NotifyDependencyChanged ? これを呼び出すと、カスタム キャッシュ依存関係のインスタンスに依存しているキャッシュ項目の有効期限が切れます。

次のサンプルでは、後の 2 つのメンバを使用します。このサンプルは、ブログやその他のソースから RSS (Really Simple Syndication) フィードを読み取り、結果を XmlDocument としてキャッシュするカスタム キャッシュ依存関係の作成方法を示しています。その後、この XMLDocument を ASP.NET Xml コントロールで使用して、ブログの内容を表示します (XSL スタイル シートを使用して HTML に変換します)。

独自のカスタム キャッシュ依存関係を作成するには、最初に、CacheDependency クラスを継承する新しいクラスを作成します。完全限定名を使用しない場合は、System.Web.Caching 名前空間をインポートする必要があります。

メモ   このほかにも、Timer クラスにアクセスするために System.Threading を、XmlDocument クラスにアクセスするために System.Xml をそれぞれインポートします。

このクラスの定義は次のようになります。

Imports System
Imports System.Web
Imports System.Threading
Imports System.Web.Caching
Imports System.Xml

Namespace v2_Caching

   Public Class BlogCacheDependency
      Inherits CacheDependency

   ' ここにクラス コードを挿入します。

End Namespace

次に、メンバ変数をいくつか追加します。

' Timer は、別のスレッドで依存関係をチェックするために使用します。
Shared TickTock As Timer
Dim m_PollTimeSec As Integer = 300
Dim m_RSS As XmlDocument
Dim m_Feed As String

Timer クラスは、変更がないかどうか RSS フィードをポーリングするために使用します。その他の変数は、ポーリング間隔、最初の RSS を格納する XmlDocument、および RSS フィードの URL です。

XmlDocument を表示に利用できるようにするために、パブリック プロパティを追加します。

Public ReadOnly Property RSS() As XmlDocument
   Get
      Return m_RSS
   End Get
End Property

RSS をクライアントに返すことだけが目的なので、このプロパティは ReadOnly で、Set アクセサはありません。

次に、BlogCacheDependency クラスの主要部分となる 2 つの要素、コンストラクタと CheckDependencyCallback メソッドについて見てみましょう。コンストラクタは次のようになります。

' BlogCacheDependency のコンストラクタ
Public Sub New(ByVal Feed As String, ByVal PollTimeSec As Integer)
   m_Feed = Feed
   m_PollTimeSec = PollTimeSec

   m_RSS = GetRSS(m_Feed)

   If TickTock Is Nothing Then
      TickTock = New Timer(New _
         TimerCallback(AddressOf CheckDependencyCallback), Me, _
         (m_PollTimeSec * 1000), (m_PollTimeSec * 1000))
   End If

End Sub

コンストラクタは、RSS フィードの取得およびチェックのための URL を含む文字列と、フィードに対するポーリング要求の間隔 (秒) を表す整数を受け取ります。これらの値はローカル メンバ変数に格納されます。次に、GetRSS メソッドを呼び出して、結果を m_RSS メンバ変数に格納します (GetRSS メソッドについてはこの後で説明します)。最後に、Timer メンバ変数がインスタンス化されているかどうかを確認し、インスタンス化されていなかった場合は新しいインスタンスを作成して、m_PollTimeSec メンバ変数の値に基づいて定期的に CheckDependencyCallback メソッドを呼び出すように設定します。

CheckDependencyCallback (以下のコードを参照) は、BlogCacheDependency 型のローカル インスタンス変数 (Sender 引数から CheckDependencyCallback に割り当てられる) を作成し、新しい XmlDocument を作成して、GetRSS メソッドから返された値を設定します。次に、新しい XmlDocumentOuterXml プロパティの値を m_RSS メンバ変数 (コンストラクタで取得した元の XmlDocument を含む) の同じプロパティの値と比較します。2 つの値が同じだった場合は、何も行われません。値が異なっていた場合は、キャッシュされている項目を削除するよう ASP.NET のキャッシュ エンジンに伝えるために、NotifyDependencyChanged が呼び出されます。

Public Sub CheckDependencyCallback(ByVal Sender As Object)
   Dim BCD As BlogCacheDependency = Sender

   Dim NewRSS As XmlDocument = GetRSS(m_Feed)
   If Not NewRSS.OuterXml = m_RSS.OuterXml Then
      BCD.NotifyDependencyChanged(BCD, EventArgs.Empty)
   End If
End Sub

GetRSS メソッドはとても単純です。

Function GetRSS(ByVal Feed As String) As XmlDocument
   Dim RSSDoc As New XmlDocument
   RSSDoc.Load(m_Feed)
   Return RSSDoc
End Function

このメソッドは、XmlDocument 型のローカル変数を作成し、XmlDocumentLoad メソッドを呼び出して m_Feed 変数に格納されている URL を渡し、呼び出し元に XmlDocument を返します。このコードからわかるように、RSS フィードを取得するのはとても簡単です。RSS は XML なので、ほんの数行のコードを書くだけで簡単に取得できます。また、XSL を使って RSS フィードを表示のために書式設定するのもとても簡単です。これについては後ほど説明します。

最後に、カスタム依存関係クラスを破棄するときには多少のクリーンアップが必要になります。クリーンアップでは、DependencyDispose メソッドをオーバーライドして、その中で Timer のインスタンスを破棄します。

Protected Overrides Sub DependencyDispose()
   TickTock = Nothing
   MyBase.DependencyDispose()
End Sub

以上でカスタム キャッシュ依存関係クラスのコードが完成しました。まだ 50 行も記述していませんが、これですべて完了です。ASP.NET 2.0 の新しいコード コンパイル機能によって、このクラスを Web サイトの Code フォルダに保存するだけで、あとは ASP.NET が自動的にコンパイルしてくれます。

カスタム キャッシュ依存関係を使用するのはさらに簡単です。以下は、カスタム キャッシュ依存関係クラスを使用する ASP.NET ページの例です。このページは、ASP.NET Xml コントロールを使用して RSS フィードを取得および表示します。コードではまず、入力された URL について、キャッシュ内に項目があるかどうかをテストします。項目がなかった場合は、項目がキャッシュから取得されたのではないことを示すラベル テキストを設定し、BlogCacheDependency クラスの新しいインスタンスを作成して、RSS フィードの URL とポーリング間隔の秒数 (この場合は 3600 秒 (1 時間)) を渡します。次に、ユーザーが指定した URL をキーに、依存関係インスタンスの RSS プロパティを値にして Cache に項目を挿入し、キャッシュ項目が依存関係のインスタンスに依存するように設定します。最後に、Xml コントロールの Document プロパティをキャッシュ項目に設定し、TransformSource プロパティを "feeds.xsl" に設定します。"feeds.xsl" は、RSS を HTML に変換するための XSL スタイル シートで、today.icantfocus.com の Web サイト (http://today.icantfocus.com/blog/archives/entries/000430/) にあります。

<%@ page language="VB" %>
<%@ import namespace="v2_Caching" %>

<script runat="server">
   
   Sub Button1_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs)
   
      Label2.Text = "Yes"
   
      If Cache(TextBox1.Text) Is Nothing Then
         Label2.Text = "No"
         
         ' 変更がないかどうか 60 分ごとにポーリングする依存関係を作成します。
         Dim dep As New BlogCacheDependency(TextBox1.Text, 3600)
   
         Cache.Insert(TextBox1.Text, dep.RSS, dep)
   
      End If
   
      FeedXml.Document = Cache(TextBox1.Text)
      FeedXml.TransformSource = "feeds.xsl"
   
   End Sub
</script>

<html>
<head runat="server">
   <title>Custom Cache Dependency Example</title>
</head>
<body>
   <form runat="server">
      Enter the RSS feed to display:
      <asp:textbox id="TextBox1" runat="server">
      </asp:textbox>
      <asp:button id="Button1" 
         onclick="Button1_Click" runat="server" text="Get Feed" />
      <hr />
      Served from cache:
      <asp:label id="Label2" runat="server" forecolor="#FF3366">
      </asp:label>
      <br />
      Feed:
      <asp:xml id="FeedXml" runat="Server" />
      <br />
   </form>
</body>
</html>

このページは図 1 のように表示されます。

Dd229384.aspnetwhidbey_caching01(ja-jp,MSDN.10).gif

図 1. CustomDependency.aspx

ユーザーが有効なフィード URL (このサンプルでは URL の確認は行っていないので、ユーザーが無効な URL を入力すると例外が発生します) を入力し、[Get Feed] ボタンをクリックすると、図 2 のような結果が表示されます (フィードがキャッシュから取得されていない点に注目してください)。

Dd229384.aspnetwhidbey_caching02(ja-jp,MSDN.10).gif

図 2. ブログ フィードの表示

その後の要求では、図 3 のように、フィードがキャッシュから返されます。

Dd229384.aspnetwhidbey_caching03(ja-jp,MSDN.10).gif

図 3. キャッシュから返されたフィード

フィードに変更があった場合は、次のポーリング間隔でキャッシュ項目が Timer のコードによって無効にされるため、結果は図 4 のようになります。

Dd229384.aspnetwhidbey_caching04(ja-jp,MSDN.10).gif

図 4. 更新されたフィード

最後に注意点として、カスタム キャッシュ依存関係の開発で外部リソース (特に自分の管理下にないリソース) をポーリングするコードを作成する際には、以下のことを頭に入れておいてください。

  • ポーリングのメカニズムによってそのリソースにかかる負荷に配慮する必要があります。他の人がホストしている RSS フィードを 30 秒ごとにポーリングしたりすれば、ひんしゅくを買うことになります (たいていの RSS フィードはそれほど頻繁に更新されません)。
  • ポーリングする RSS フィードが利用できなくなる可能性もあるので、そのような場合のために例外処理コードを含める必要があります。

SQL キャッシュの無効化

データと出力の両方をキャッシュすることで得られる Web 開発者にとっての最大のメリットは、要求のたびに貴重なリソースを使って処理やデータの取得が行われることによるコストを回避できるという点にあります。言うまでもなく、最もコストの高いものの 1 つがデータベース アクセスです。したがって、データセットのコピーやデータドリブンなページの出力をメモリ内に格納する場合、キャッシュをとりわけ有効に活用できます。

ただし、最新のデータが必要なページがある場合は問題になります。ASP.NET v1.1 では、キャッシュされているデータが古くならないようにするにはキャッシュの有効期間を極端に短くするか、ページの変更されない箇所だけを部分的にキャッシュするしかありませんでした。

ASP.NET 2.0 ではこの点が改善されて、キャッシュ項目や出力キャッシュのページを Microsoft(R) SQL Server? のデータの変更に基づいて無効にできるようになりました。この機能は SQL Server 7、2000、および 2005 でサポートされており、実装するのもきわめて簡単です。

SQL Server 7/2000

SQL Server 7 と SQL Server 2000 でデータベース キャッシュの無効化を実現するにあたって ASP.NET チームで問題となったのは、どちらのバージョンにも、特定のテーブルのデータが変更されたことを関係者に通知するためのメカニズムが組み込まれていないことでした。確かに、トリガを使用してこれを実現することは可能ですが (たとえば筆者は、トリガを使用して sp_MakeWebTask を呼び出して、当時ファイル キャッシュ依存関係として使用していたページに SQL Server Web Assistant を使ってテーブルの内容を書き込んでいました)、パフォーマンスが改善するどころか、サイトがまったく機能しなくなるような事態を招く危険性があります。

この問題は、前の例で取り上げた機能を土台にして解決されています。つまり、ASP.NET チームは、指定されたデータベース テーブルを指定されたスケジュールでポーリングして変更がないかどうかを確認する、SqlCacheDependency クラスというカスタム キャッシュ依存関係を考案したのです。データの変更が検出されるとキャッシュが無効になるため、最新のデータを取得できます。

ポーリングと比較のメカニズムをもう少し単純にするために、SQL 7/2000 の SQL キャッシュの無効化では、変更を監視する各データベースやテーブルでそれぞれ 1 回だけセットアップを行う必要があります。このセットアップには、コマンド ライン ツールの aspnet_regsqlcache.exe を使用します (この機能は ASP.NET 2.0 のアルファ リリースでは独立していますが、ベータ リリースでは、ウィザードとコマンド ラインの両方の UI を備えた aspnet_regsql.exe という別のツールに含まれる予定です)。aspnet_regsqlcache.exe は、ASP.NET 2.0 リリースのフレームワークの基本ディレクトリにあります。

Pubs サンプル データベースでキャッシュの無効化を有効にするのはごく簡単で、次のコマンドを実行するだけです。-S パラメータで SQL Server のローカル インスタンスを指定し、-E パラメータで、信頼関係接続を使って SQL Server に接続するように指定します (ログイン アカウントに SQL Server コンピュータと目的のデータベースへのアクセス権が必要です)。

aspnet_regsqlcache.exe -S (local) -E -d Pubs ?ed

Authors テーブルについても、次のコマンドを実行するだけで簡単にキャッシュの無効化を有効にできます。

aspnet_regsqlcache.exe -S (local) -E -d Pubs -t Authors ?et

次のコマンドを実行します。

aspnet_regsqlcache.exe -?

使用可能なコマンド ライン パラメータがすべて表示されます。

データベースのセットアップが完了したら、次に接続情報とポーリングの頻度を指定する必要があります。これは、次のように、<cache> という web.config の新しい構成要素を使って行います (アプリケーションで使用する接続文字列を格納するには、同じく ASP.NET 2.0 で新たに導入された connectionStrings セクションを使用します。ベータ リリースではユーザー ID とパスワード情報の暗号化のサポートが追加される予定です)。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

   <connectionStrings>
      <add 
         name="PubsConnYukon" 
         connectionString="User ID=<id>;pwd=<password>;Initial 
Catalog=pubs;Data Source=192.168.0.108" />
      <add 
         name="PubsConn" 
         connectionString="Initial Catalog=pubs;Data 
Source=(local);Trusted_Connection=true" />
   </connectionStrings>

    <system.web>
    
      <cache>
         <sqlCacheDependency enabled="true" pollTime="30000">
            <databases>
               <add name="Pubs" connectionStringName="PubsConn" />
            </databases>
         </sqlCacheDependency>
      </cache>

    </system.web>

</configuration>

上の例では、<sqlCacheDependency> 要素を使用してデータベース キャッシュ依存関係を有効にし (このアプリケーションのグローバル設定)、既定のポーリング間隔を 30000 ミリ秒 (30 秒) に設定しています。<databases> 要素には、このアプリケーションに対してセットアップするデータベースをすべて含めます。この例では、<add> 要素は 1 つだけで、connectionStringName 属性を使用して Pubs データベースを追加しています (connectionString 属性を使用して直接 <add> 要素で接続文字列を指定することもできます)。name 属性は、後でこの依存関係を参照する際に使用する名前を指定します。

いったん <cache> 要素の構成が完了すれば、構成済みの依存関係を使用するのはとても簡単です。依存関係を設定するには、Output Cache API、Cache API、宣言の 3 つの方法があります。

次のコードは、Output Cache API によってこの依存関係を使用する方法を示しています。これにより、Authors テーブルのデータが変更されると、ページの出力キャッシュが無効になります。

メモ   このコードは、System.Web.Caching 名前空間がインポートされていることを前提としています。

Dim sqlDependency As New SqlCacheDependency("Pubs", "Authors")
Response.AddCacheDependency(sqlDependency)
Response.Cache.SetValidUntilExpires(True)
Response.Cache.SetExpires(DateTime.Now.AddMinutes(60))
Response.Cache.SetCacheability(HttpCacheability.Public)

通常このコードは、Page_Load イベント ハンドラに配置されます。

構成済みの依存関係を Cache クラスを使って使用する場合は、次のようなおなじみのコードを使用します。このコードは、Pubs Authors テーブルからデータセットを取得し、それを構成済みの SqlCacheDependency を使ってキャッシュします。データが変更されていない場合は、キャッシュされているデータセットをそのまま返します。

Dim Key As String = "Authors"

If (Cache(Key) Is Nothing) Then

   Label1.Text = "Not in Cache…"

   Dim connection As New _
      SqlConnection(ConfigurationSettings.ConnectionStrings("Pubs"))
   Dim adapter As New _
      SqlDataAdapter("SELECT * FROM Authors", connection)
   Dim DS As New DataSet
   adapter.Fill(dataSet)

   Cache.Insert(Key, DS, New SqlCacheDependency("Pubs", "Products"))

Else
   Label1.Text = "In Cache…"

End If

Return Cache(Key)

最後の方法として、単純に @ OutputCache 宣言ディレクティブを使用してページ出力全体をキャッシュし、新しい sqlDependency 属性を使用して依存関係を指定することもできます。依存関係は、DatabaseAlias:TableName という形式で指定します (DatabaseAlias は、web.config で設定した <add> 要素の name 属性で指定されている名前、TableName は変更されていないかどうかポーリングするテーブルの名前です)。次のコードによって表示されるページは、Authors テーブルからデータを抽出し、それを GridView コントロールに連結し、構成済みの依存関係に基づいてページの出力をキャッシュします。

<%@ page language="VB" %>
<%@ outputcache 
   duration="9999" varybyparam="none" sqldependency="Pubs:Authors" %>

<script runat="server">
   Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
      Sqldatasource1.ConnectionString = _
         ConfigurationSettings.ConnectionStrings("PubsConn")
      CacheStatus.Text = "This page was last rendered at: " & _
         DateTime.Now.ToLongTimeString
   End Sub
</script>

<html>
<head runat="server">
   <title>DB Cache invalidation on SQL Server 2000</title>
</head>
<body>
   <form runat="server">
      <h2><asp:label id="CacheStatus" runat="Server"/></h2>
      <asp:gridview id="Gridview1" 
         datasourceid="Sqldatasource1" bordercolor="#CC9966"
         borderstyle="None" borderwidth="1px" backcolor="White" 
         cellpadding="4" runat="server">
         <alternatingrowstyle font-italic="False" font-bold="False">
         </alternatingrowstyle>
         <rowstyle forecolor="#330099" backcolor="White" 
            font-italic="False" font-bold="False">
         </rowstyle>
         <headerstyle forecolor="#FFFFCC" backcolor="#990000" 
            font-italic="False" font-bold="True">
         </headerstyle>
         <footerstyle forecolor="#330099" backcolor="#FFFFCC" 
            font-italic="False" font-bold="False">
         </footerstyle>
      </asp:gridview>
      <asp:sqldatasource id="Sqldatasource1" runat="server" 
         selectcommand="SELECT * FROM authors" 
         providername="System.Data.SqlClient" >
      </asp:sqldatasource>
   </form>
</body>
</html>

上のページ (この記事のサンプル コードにも含まれています) の出力は、Authors テーブルのデータが変更されるまでキャッシュに残ります。データが変更されると、更新されたデータが取得され、再びページ出力がキャッシュされます。

SQL Server 2005

SQL Server 2005 には新しい通知インフラストラクチャがあるため、データベース キャッシュの無効化をさらに簡単に利用できます。SQL Server 2005 のデータを使用する場合は、SqlCacheDependency クラスのコンストラクタをオーバーロードして、データの取得に使用される SqlCommand のインスタンスに基づく依存関係を構築します。これにより、SQL Server 2005 データベース サーバーからの通知メッセージを受け取るリスナ オブジェクトが作成され、アプリケーションがデータ変更通知のサブスクライバとして登録されます。この通知が送られてくると、その依存関係に関連付けられているキャッシュ項目が削除されます。

SQL Server 2005 が提供する通知サービスの利点は、データベースで特別なテーブル、トリガ、およびストアド プロシージャをセットアップする必要がなくなることです。また、ポーリングを行う必要もないため、web.config で特別な設定を行う必要もありません。SQL Server 2005 でデータベース キャッシュの無効化を利用するには、データの取得に使用する SqlCommandSqlCacheDependency クラスのインスタンスに関連付けるためのコードを 1 行追加するだけです。

Dim SqlDep As New SqlCacheDependency(SqlCmd)

あとは、SQL Server 7 や SQL Server 2000 の場合と同じように依存関係を使用できます。したがって、Output Cache を使用してページをキャッシュする場合、コードは次のようになります (このコードは、この記事のサンプル ファイルの SqlCacheInvalidation_Yukon.aspx ページでも使用されています)。

Dim SqlConn As New _
SqlConnection(ConfigurationSettings.ConnectionStrings("PubsConnYukon"))
Dim SqlCmd As New SqlCommand("SELECT * FROM Authors", SqlConn)
Dim SqlDep As New SqlCacheDependency(SqlCmd)
      
SqlConn.Open()
Gridview1.DataSource = SqlCmd.ExecuteReader()
Gridview1.DataBind()
SqlConn.Close()

Response.AddCacheDependency(sqlDependency)
Response.Cache.SetValidUntilExpires(True)
Response.Cache.SetExpires(DateTime.Now.AddMinutes(60))
Response.Cache.SetCacheability(HttpCacheability.Public)

Cache クラスを使ってデータセットをキャッシュする場合も、コードは多少違って見えますが、基本的な考え方は同じです (コマンドと SqlCacheDependency を関連付けるコードは太字になっています)。

Dim Key As String = "Authors"

If (Cache(Key) Is Nothing) Then

   Label1.Text = "Not in Cache…"

   Dim connection As New _
      SqlConnection(ConfigurationSettings.ConnectionStrings("Pubs"))
   Dim adapter As New _
      SqlDataAdapter("SELECT * FROM Authors", connection)
   Dim DS As New DataSet
   adapter.Fill(dataSet)

   Cache.Insert(Key, DS, New SqlCacheDependency(adapter.SelectCommand))

Else
   Label1.Text = "In Cache…"

End If

Return Cache(Key)

これですべて完了です。ただし、当然ながら、注意が必要な点もあります。第 1 に、SQL Server 2005 データベースのデータへのアクセスに使用するアカウントには、ターゲット データベースでクエリ通知サブスクリプションを要求するためのアクセス許可が必要です。第 2 に、SQL Server 2005 の PDC ビルドにはバグがあり、データの変更があった場合に通知が送信されるようにするためには sa アカウントを使ってデータベースにログインする必要があります。

重要   当然の理由から、どのようなアプリケーションにおいても、sa アカウントを使ってデータにアクセスしたりデータに変更を加えたりすべきではありません。したがって、SQL Server 2005 の PDC ビルドで SQL キャッシュの無効化を試みる場合は、必ず、インターネットにアクセスしないアプリケーション (Web サーバーとデータベース サーバーの両方を含む) を使用するようにしてください。

キャッシュ後の置換

ここで紹介する最後の新機能は、キャッシュ後の置換と呼ばれる機能です。この機能は、他の機能をちょうど補うものとなっています。アプリケーションにキャッシュに適さない動的なコードがほんの少し含まれているために出力キャッシュを利用できない、という場合はよくあります。そのような場合も、キャッシュ後の置換を使用すれば、プレースホルダとして機能するコントロールをページに追加し、指定したメソッドをそのコントロールに呼び出させることによって、実行時に文字列出力を取得できます。これにより、キャッシュと動的なコードの両方を利用できるようになります。

キャッシュ後の置換は、ここで紹介している新機能の中で最も使い方が簡単な機能でもあります。置換する場所を選択するには次の 2 つの方法があります。

  • 新しい Response.WriteSubstitution メソッドを呼び出して、目的の置換メソッド (コールバック メソッド) への参照を渡します。
  • <asp:substitution> コントロールをページの目的の場所に追加し、methodname 属性をコールバック メソッドの名前に設定します。

2 つ目の方法には、文字列出力を表示する場所をより正確に指定できるという利点があります。どちらの方法でも、@ OutputCache ディレクティブをページに追加して、ページ出力のキャッシュの有効期間や場所などを指定できます。キャッシュ後の置換のもう 1 つ便利な点は、この機能を利用するカスタム コントロールを作成できることです。たとえば ASP.NET チームは、AdRotator コントロールに変更を加えてキャッシュ後の置換に対応させています。これにより、AdRotator コントロールをページに追加し、そのページの出力をキャッシュすると、AdRotator コントロールでキャッシュ後の置換が自動的に行われ、指定した広告が正しく表示されます。そのために必要な作業は何もありません。

次のコードは、キャッシュ後の置換を使用するページの例です。このページは、Pubs データベースの Authors テーブルからデータを取得して、それを GridView に連結し、AdRotator コントロールの広告を表示します。このページには、ページが作成された時刻が書き込まれるラベルが (Page_Load に) 含まれています。さらに、<asp:substitution> コントロールをページに追加して、methodname 属性を Substitute に設定してあります。Substitute は目的の文字列出力 (この例では、現在の時刻を含む文字列) を返すメソッドの名前です。

<%@ page language="VB" %>
<%@ outputcache duration="30" varybyparam="none" %>

<script runat="server" language="vb">
   Shared Function Substitute(ByVal MyContext As HttpContext) As String
      Dim str As String = "I'm adding content to a cached page!<br/>"
      str &= "The Current Time is: " & DateTime.Now.ToLongTimeString
      Return str
   End Function
   
   Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
      SqlDataSource1.ConnectionString = _
         ConfigurationSettings.ConnectionStrings("PubsConn")
      Label1.Text = "Page created at: " + DateTime.Now.ToLongTimeString
   End Sub
</script>

<html>
<head id="Head1" runat="server">
   <title>Untitled Page</title>
</head>
<body>
   <form id="Form1" runat="server">
      <h1>
         <asp:label id="Label1" runat="server">Label</asp:label>
      </h1>
      <br />
      <br />
      <asp:adrotator id="AdRotator1" width="468px" height="60px" 
         advertisementfile="ads_tall.xml" runat="server" />
      <asp:substitution id="Substitution1" methodname="Substitute"
         runat="server"  />
      <br />
      <br />
      <asp:gridview id="GridView1" autogeneratecolumns="False"
         datasourceid="SqlDataSource1" datakeynames="au_id" 
         runat="server" >
         <columnfields>
            <asp:boundfield sortexpression="au_id" datafield="au_id" 
               readonly="True" headertext="Author ID">
            </asp:boundfield>
            <asp:boundfield sortexpression="au_fname"
               datafield="au_fname" headertext="First Name">
            </asp:boundfield>
            <asp:boundfield sortexpression="au_lname"
               datafield="au_lname" headertext="Last Name">
            </asp:boundfield>
         </columnfields>
         <rowstyle forecolor="#000066">
         </rowstyle>
         <headerstyle forecolor="White" backcolor="#006699" 
            font-bold="True">
         </headerstyle>
      </asp:gridview>
      <asp:sqldatasource id="SqlDataSource1" 
         providername="System.Data.SqlClient"
         selectcommand="SELECT [au_id], [au_fname], [au_lname] FROM dbo.[authors]"
         runat="server" >
      </asp:sqldatasource>
   </form>
</body>
</html>

このページを初めて呼び出すと、図 5 のような出力が返されます ("page created" ラベルの時刻と Substitute メソッドによって出力された時刻が一致している点に注目してください)。

Dd229384.aspnetwhidbey_caching05(ja-jp,MSDN.10).gif

図 5. PostCache.aspx の最初の出力

このページを再表示すると、図 6 のようになります。キャッシュされた出力であるにもかかわらず、AdRotator の出力と Substitute メソッドの出力が両方とも更新されていることがわかります。ページの残りの部分はキャッシュされたままです。

Dd229384.aspnetwhidbey_caching06(ja-jp,MSDN.10).gif

図 6. キャッシュ後の置換を使用した場合のキャッシュされた出力

この記事のサンプル ファイルには、Response.WriteSubstitution を使用した PostCache_RWS.aspx というページも含まれています。この方法に関心のある方は、こちらを参照してください。

まとめ

ASP.NET チームは、いくつかの単純な機能を追加することによって、ASP.NET の便利な機能をさらに強力なものへと進化させました。これにより、次のようなことが可能になりました。

  • データドリブン ページの出力やデータセットをキャッシュして、データが変更された場合にキャッシュを自動的に削除する。
  • カスタム キャッシュ依存関係を作成する。
  • キャッシュ後の置換によって、キャッシュされたページに対して実行時に動的にテキストを追加する。

これでもう、ASP.NET アプリケーション開発者がキャッシュを利用しない理由はなくなりました。次世代の ASP.NET アプリケーションのパフォーマンスは真に他を圧倒するものとなることでしょう。

参考書籍

執筆者紹介

G. Andrew Duthie 氏は、Graymad Enterprises, Inc. Non-MS link の創業者兼社長であり、Microsoft の Web 開発技術のトレーニングとコンサルティングを提供しています。氏は、Active Server Pages が発表された当初から多層 Web アプリケーションの開発を行っています。また、『Microsoft ASP.NET Programming with Microsoft Visual Basic』、『Microsoft ASP.NET Programming with Microsoft Visual C#』、『ASP.NET in a Nutshell』(第 2 版) を始めとして、ASP.NET 関連の著書も多数あります。このほか、Software Development、Dev-Connections 関連のカンファレンス、Microsoft Developer Days、VSLive! など、数々のイベントで積極的な講演活動を展開しています。また、International .NET Association (INETA) Non-MS link Speaker's Bureau のメンバーとして .NET ユーザー グループでも講演を行っています。Duthie 氏について詳しくは、Graymad Enterprises, Inc. の Web サイト (http://www.graymad.com/ Non-MS link) を参照してください。

執筆者あとがき

この場をお借りして、ASP.NET チームの常駐のキャッシュ専門家である Rob Howard 氏への感謝の意を表したいと思います。氏はこの記事の執筆に協力してくれただけでなく、この記事のサンプルの出発点となったコード サンプルも提供してくれました。