WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

HTTP ハンドラでキャッシュコントロール

by WebSurfer 19. May 2015 17:55

このブログでは、アプリケーションのフォルダに保存した画像やスクリプトファイルなどを応答として返すのに HTTP ハンドラを使っています。

F5 キーを押すなどしてページをリロードすると、下の画像のように HTTP 304 Not Modified 応答を返しますが、これを HTTP ハンドラで実現するにはどのようにするかという話をメインに、HTTP ハンドラを利用したキャッシュコントロールについて書きます。

HTTP 304 応答

HTTP ハンドラを使わないで、以下のように直接 img 要素の src 属性に画像ファイルの URL を設定した場合は、IIS が ETag, Last-Modified に設定する値を当該ファイルから取得し、それらを応答ヘッダに含めて返してくれます。

<img alt="" src="/images/sample2.jpg" />

クライアントが F5 キーなどでページをリロードすると、再度ブラウザが src 属性に設定された URL の画像を要求しますが、そのとき If-Modified-Since と If-None-Match を要求ヘッダに含めて送ってくれます。

要求を受けた IIS は要求ヘッダに含まれる If-Modified-Since と If-None-Match を見て、条件を満たせば(即ち、画像が変更されてなければ) HTTP 304 Not Modified 応答を返します(ヘッダのみでコンテンツは送信しない。ブラウザのキャッシュを使えという指示と同じ)。

ちなみに、ブラウザのキャッシュを削除すると、画像を要求する際 If-Modified-Since と If-None-Match は要求ヘッダには含まれなくなます。なので、キャッシュを削除してから F5 キーを押せば、IIS は画像のコンテンツを含めて HTTP 200 応答を返します。

src 属性に HTTP ハンドラを指定する(HTTP ハンドラ経由でファイルをダウンロードする)場合は、IIS が自動的に行ってくれる上記の処置を、自力で HTTP ハンドラにコーディングすることになります。

簡単に書くと、(1) ブラウザ送られてくる要求ヘッダの中の If-None-Match, If-Modified-Since の値を取得し、その値に応じて HTTP 200 応答(コンテンツを含む)または HTTP 304 応答(コンテンツ無し)のどちらかを返す。(2) 要求を受けたら毎回、応答ヘッダの ETag, Last-Modified に設定する値を当該ファイルから取得し、それらを応答ヘッダに含めてブラウザへ返す・・・という処置をコーディングすることになります。

さらに、せっかく HTTP ハンドラを使って細かくキャッシュコントロールのためのコードが書けるのですから、上記 (1), (2) だけでなく(ETag, Last Modified の応答ヘッダへの設定だけでなく)、Cache-Control, Expires 等の応答ヘッダへの設定も行って、プロキシやブラウザのキャッシュをきちんとコントロールした方がよさそうです。

また、サーバー側でのキャッシュもコントロールすることが可能になりますので、サーバーのキャッシュが利用できればそれを使わない手はないですよね。

ということで、例として、画像ファイル用とスクリプトファイル用それぞれの HTTP ハンドラを、Web サイトプロジェクトの ジェネリックハンドラ(.ashx ファイル)の形で書いたコードを以下に紹介します。ベースは BlogEngine 1.6.1.0 のものです。

(1) 画像用の HTTP ハンドラ

<%@ WebHandler Language="C#" Class="_0114_ImageHandler" %>

using System;
using System.Web;
using System.IO;
using System.Net;

public class _0114_ImageHandler : IHttpHandler {
    
  public void ProcessRequest (HttpContext context) 
  {
    HttpResponse response = context.Response;
    HttpRequest request = context.Request;
        
    // ファイル名はクエリ文字列で指定。
    string fileName = request.QueryString["picture"];
        
    // 画像ファイルはアプリケーションルート直下の
    // images という名前のフォルダにあることが前提。
    string folder = "~/images/";

    if (!String.IsNullOrEmpty(fileName))
    {            
      string path = 
          context.Server.MapPath(folder + fileName);
      FileInfo fileInfo = new FileInfo(path);
      if (fileInfo.Exists)
      {            
        // BlogEngine では何故か作成日時 (CreationTimeUtc) が
        // 使われていたが、IIS が設定するのと同様に、更新日時
        // (LastWriteTimeUtc) を使用するように変更。
        DateTime lastWriteTime = fileInfo.LastWriteTimeUtc;

        // ETag は更新日時のタイマ刻み数。(IIS とは異なる)
        string etag = "\"" + lastWriteTime.Ticks + "\"";
        string ifNoneMatch = request.Headers["If-None-Match"];

        DateTime ifModifiedSince = DateTime.MinValue;
        DateTime.TryParse(
            request.Headers["If-Modified-Since"], 
            out ifModifiedSince);

        response.Clear();
        // ブラウザとプロキシにキャッシュを許可
        response.Cache.SetCacheability(HttpCacheability.Public);
        // 有効期限を、要求を受けた日時から 1 年に設定。
        response.Cache.SetExpires(DateTime.Now.AddYears(1));
        // Last-Modified を応答ヘッダに設定。
        response.Cache.SetLastModified(lastWriteTime);
        // ETag を応答ヘッダに設定。
        response.Cache.SetETag(etag);

        // 要求ヘッダの If-None-Match, If-Modified-Since と、
        // 上のコードで取得した etag, lastWriteTime を比較し、
        // どちらかが一致したら HTTP 304 応答を返して終了。
        if (ifNoneMatch == etag || 
            ifModifiedSince == lastWriteTime)
        {
          response.StatusCode = 
              (int)HttpStatusCode.NotModified;
          return;
        }

        int index = fileName.LastIndexOf(".") + 1;
        string extension = 
            fileName.Substring(index).ToLowerInvariant();

        // IE は image/jpg を認識しないことへの対応
        if (extension == "jpg")
        {
          context.Response.ContentType = "image/jpeg";
        }
        else
        {
          context.Response.ContentType = "image/" + extension;
        }
                
        response.TransmitFile(fileInfo.FullName);
      }
    }
  }
 
  public bool IsReusable 
  {
    get 
    {
      return false;
    }
  }
}

ETag, Last Modified, Expires の違いについては以下の stackoverflow の記事が参考になると思います。

(2) スクリプト用の HTTP ハンドラ

<%@ WebHandler Language="C#" Class="_0114_ScriptHandler" %>

using System;
using System.Web;
using System.IO;
using System.Security;
using System.Web.Caching;
using System.Net;

public class _0114_ScriptHandler : IHttpHandler 
{
    
  public void ProcessRequest (HttpContext context) 
  {
    HttpResponse response = context.Response;
    HttpRequest request = context.Request;

    // ファイル名はクエリ文字列で指定。
    string fileName = request.QueryString["filename"];

    // スクリプトファイルはアプリケーションルート直下の
    // scripts と言う名前のフォルダにあることが前提。
    string folder = "~/scripts/";

    if (!String.IsNullOrEmpty(fileName))
    {
      string script = null;
            
      // まずサーバーのCacheをチェック。なければファイルに
      // アクセスしてスクリプトを取得。
      if (context.Cache[fileName] == null)
      {
        if (!fileName.EndsWith(".js",
            StringComparison.OrdinalIgnoreCase))
        {
          throw new SecurityException("アクセス不可");
        }

        string path =
            context.Server.MapPath(folder + fileName);
        FileInfo fileInfo = new FileInfo(path);

        if (fileInfo.Exists)
        {
          using (StreamReader reader = 
                              new StreamReader(path))
          {
            script = reader.ReadToEnd();
                        
            // Minify は割愛。
                        
            // サーバーのキャッシュに保持。
            context.Cache.Insert(
                        fileName,
                        script,
                        new CacheDependency(path));
          }
        }
      }
      else
      {
        script = (string)context.Cache[fileName];
      }

      if (!string.IsNullOrEmpty(script))
      {
        response.Clear();
        response.ContentType = "text/javascript";

        // Vary: Accept-Encoding をヘッダに設定。
        response.Cache.VaryByHeaders["Accept-Encoding"] = true;

        // 有効期限 (Expires ヘッダ) を 7 日に設定。
        response.Cache.SetExpires(
                DateTime.Now.ToUniversalTime().AddDays(7));
        // Cache-Control ヘッダの max-age を 7 日(604800 秒)
        // に設定。
        response.Cache.SetMaxAge(new TimeSpan(7, 0, 0, 0));
        // Cache-Control ヘッダに must-revalidate を設定。
        response.Cache.SetRevalidation(
                HttpCacheRevalidation.AllCaches);

        int hash = script.GetHashCode();
                
        string etag = "\"" + hash.ToString() + "\"";
        string ifNoneMatch = request.Headers["If-None-Match"];
                
        // ETag を応答ヘッダに設定。
        response.Cache.SetETag(etag);
        // Cache-Control ヘッダに public を設定(ブラウザと
        // ���ロキシにキャッシュを許可)
        response.Cache.SetCacheability(HttpCacheability.Public);
                
        // 要求ヘッダの If-None-Match と上のコードで取得した
        // etag を比較し一致したら HTTP 304 応答を返して終了。
        // サーバーの Cache を利用する関係で Last Modified
        // は使用しない。
        if (ifNoneMatch == etag)
        {
          response.StatusCode = 
                      (int)HttpStatusCode.NotModified;
          return;
        }

        response.Write(script);
      }            
    }
  }
 
  public bool IsReusable 
  {
    get 
    {
      return false;
    }
  }
}

ファイルから取得したスクリプトの文字列をサーバーのキャッシュに保持するようにし、2 回目以降の要求ではキャッシュから取得するようにしています。

キャッシュから取得する場合はスクリプトファイルの更新日時を取得できないので、Last Modified は使わず ETag のみを応答ヘッダに含めています。

ETag の値は、スクリプトの文字列から String.GetHashCode メソッドでハッシュコードを取得し、それを設定しています。

Tags:

Cache

@OutputCache ディレクティブ

by WebSurfer 28. April 2012 16:50

@ OutputCache ディレクティブ を使ったキャッシュの設定方法は、MSDN ライブラリなどを検索するといろいろ見つかりますが、結局読んでもよく分かりません。(笑)

という訳で、実際にコードを書いて試して見ました。今回調べたのは Location 属性の設定によってどう変わるかということです。いろいろ発見があったので、忘れないように書いておきます。

まず、@ OutputCache ディレクティブを設定しない場合ですが、その場合はサーバーではキャッシュされません。

応答ヘッダは以下のようになりますので、コンテンツはブラウザのユーザー専用キャッシュにのみキャッシュされます。

Cache-Control: private
Date: Sat, 28 Apr 2012 01:44:22 GMT

ただし、ASP.NET ページには有効期限がないため、キャッシュは常に古いと見なされます。そのため、ASPX リソースをリクエストすると、ページがキャッシュされてない場合と同じように、常にサーバーから新しいリソースを取得することになります。

次に、例として、@ OutputCache ディレクティブを以下のように設定した場合、キャッシュがどのように設定されるかを書きます。

<%@ OutputCache 
    Duration="15" 
    Location="xxxxx" 
    VaryByParam="None" %>

Location 属性の xxxxx のところには、None, Client, Downstream, Server, ServerAndClient, Any(デフォルト)のいずれかを指定します。結果はそれぞれ以下のようになります。

(1) None

ブラウザ、プロキシ、サーバーいずれでもキャッシュされません。

ブラウザおよびプロキシにキャッシュの方法を指示する HTTP 応答ヘッダは以下のようになります。

Cache-Control: no-cache
Date: Sat, 28 Apr 2012 01:45:43 GMT
Expires: -1
Pragma: no-cache

なお、Duration と VaryByParam は必須属性ですが、Location="None" と設定してあると Duration を設定しなくてもエラーにはなりません。逆に、Duration が設定してあっても無視されるようです。

(2) Client

ブラウザのみでキャッシュされます。サーバーではキャッシュされません。

応答ヘッダは以下のようになります。

Cache-Control: private, max-age=15
Date: Sat, 28 Apr 2012 02:49:17 GMT
Expires: Sat, 28 Apr 2012 02:49:32 GMT

(3) Downstream

ブラウザおよびプロキシ(もしあれば)のみでキャッシュされます。サーバーではキャッシュされません。

応答ヘッダは以下のようになります。

Cache-Control: public, max-age=15
Date: Sat, 28 Apr 2012 02:56:09 GMT
Expires: Sat, 28 Apr 2012 02:56:24 GMT

(4) Server

サーバーのみでキャッシュされます。Duration 属性に指定した時間が経過すると、キャッシュは破棄されます。その後に要求を受けると、その応答コンテンツが再びキャッシュされます。

応答ヘッダは以下のようになりますので、ブラウザとプロキシではキャッシュされません。

Cache-Control: no-cache
Date: Sat, 28 Apr 2012 02:28:31 GMT
Expires: -1
Pragma: no-cache

なお、サーバーキャッシュは GET 用と POST 用の 2 種類生成されることに注意してください。下に示した検証用のコードで、Duration を十分長い時間にとって、GET で要求した時と、POST で要求した時とで、ラベルに表示される時間を比較してみてください。違う値になるはずです。

(5) ServerAndClient

サーバーとブラウザでキャッシュされます。

応答ヘッダは以下のようになります(Client の場合と同様)。

Cache-Control: private, max-age=15
Date: Sat, 28 Apr 2012 02:37:17 GMT
Expires: Sat, 28 Apr 2012 02:37:32 GMT

(6) Any

ブラウザ、プロキシ、サーバーのすべてでキャッシュされます。

応答ヘッダは以下のようになります(DownStream の場合と同様)。

Cache-Control: public, max-age=15
Date: Sat, 28 Apr 2012 02:18:21 GMT
Expires: Sat, 28 Apr 2012 02:18:36 GMT

なお、Location 属性はデフォルトで Any ですので、Location="Any" を削除しても結果は同じになります。

上に述べた、検証に使ったコードは以下の通りです。

<%@ Page Language="C#" %>
<%@ OutputCache Duration="3600" 
    Location="Server" 
    VaryByParam="None" %> 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    protected void Page_Load(object sender, EventArgs e)
    {
        Label1.Text = DateTime.Now.ToString();
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="Button1" 
            runat="server" 
            Text="Button" />
        <asp:Label ID="Label1" runat="server">
        </asp:Label>
    </div>
    </form>
</body>
</html>

最後に、今回参考にしたキャッシュ関係の記事のリンクを張っておきます。

Web 2.0 アプリケーションのパフォーマンスを改善する

プロキシキャッシュ対策

ブラウザキャッシュでパフォーマンス向上

Tags:

Cache

SQL キャッシュ依存関係

by WebSurfer 28. March 2011 21:44

SQL キャッシュ依存関係の検証

SQL キャッシュ依存関係 (SQL Cache Dependency) について調べて、いろいろわかったことがありますので、備忘録として書いておきます。自分なりに解釈したところもありますので、ひょっとしたら間違っているところがあるかもしれませんが。(汗)

まず、キャッシュ全般に関する予備知識として、MSDN ライブラリの ASP.NET のキャッシュの概要 に一通り目を通しておくことをお勧めします。

ASP.NET のキャッシュは、ファイル、データベースなどの他の要素に依存するように構成でき、依存する要素が変更されると、ASP.NET は関係する項目をキャッシュから削除するように設定できます。そうすることで、最新のデータをユーザーに提供するとともに、キャッシュの内容も最新に保つことができます。

SQL キャッシュ依存関係とは、ASP.NET のキャッシュと SQL Server DB のテーブルやレコードとの間に依存関係を持たせ、当該テーブル/レコードが変更されたら ASP.NET のキャッシュを削除し、次のリクエストでは新しいデータを DB から取得してユーザーに提供するとともに、新しいデータをキャッシュできるようにするための機能です。

そのために、SQL Server 側で依存する項目が変更されたら、その通知を SQL Server から ASP.NET に渡してやる必要があります。通知を渡す仕組みに以下の 2 種類があります。

  1. テーブルポーリング (table polling)
  2. クエリ通知 (query notification)

ここでは、前者の「ポーリング」を利用した場合について、その仕組みとアプリケーションの作成例を書きます。実は、「クエリ通知」の方は、SQL Server 関係の設定が難しく、まだうまくいってませんので。(笑)

ポーリングは、事前に設定したインターバルで自動的に ASP.NET から SQL Server に変更の有無を問い合わせる仕組みです。

ポーリングを行うためには、SQL Server 側と ASP.NET 側の両方に準備が必要です。

まず、SQL Server 側の準備です。SQL Server 側でポーリングによる通知を有効にするには、aspnet_regsql.exe コマンドラインツールを使用し、コマンドラインから以下のように実行します。

aspnet_regsql -S <サーバー名> -E -d <DB 名> -ed
aspnet_regsql -S <サーバー名> -E -t <テーブル名> -d <DB 名> -et

オプションのパラメータの設定方法詳細については MSDN ライブラリ を参照してください。SQL キャッシュ依存関係オプションは SQL Server 7.0, 2000, 2005 と書いてありますが、SQL Server 2008 も同じです。

aspnet_regsql.exe の実行

サーバー名のところは、例えば SQL Server をインストールしてあるコンピュータ名が papiko-pc で SQL Server が Express Edition のときは papiko-pc\sqlexpress とします。成功すると左の画像に示したようになります(クリックすると拡大画像が表示されます)。

開発環境で SQL Server Developer Eidtion と Express Edition の両方をインストールしてある場合など、インスタンスが複数ある場合、インスタンス名(上記の場合 sqlexpress)も指定するところがミソです。

省略すると既定のインスタンス(MSSQLSERVER)を指定したとみなされるらしく、Developer Edition の方に設定されてしまいます。自分はこれに気づかず、半日ぐらいハマってしまいました。(笑)

生成されたポーリング先のテーブル

aspnet_regsql.exe を実行することによって、SQL Server にポーリング先のテーブル、ポーリングに使うストアドプロシージャ、更新の検出のためのトリガを生成するストアドプロシージャなどが自動生成されます。

ポーリング先のテーブルは AspNet_SqlCacheTablesForChangeNotification という名前で、tableName, notificationCreated, changedId という 3 つのフィールドを持ちます。左の画像(クリックすると拡大表示されます)は、SQL Server Management Studio でそのテーブルを表示したものです。

生成されたストアドプロシージャ

ポーリング先のテーブルに加えて、左の画像(クリックすると拡大表示されます)に示す 5 つのストアドプロシージャが自動生成されます。

これらは SQL Server に更新状況を問い合わせたり、更新の検出のためのトリガを生成するために使用されます。

ポーリングに使うのは SqlCachePollingStoredProcedure という名前のストアドプロシージャで、以下のような内容になっています。これが web.config で設定したポーリング間隔で呼び出されます。

ALTER PROCEDURE [dbo].[AspNet_SqlCachePollingStoredProcedure] AS
SELECT tableName, changeId 
FROM dbo.AspNet_SqlCacheTablesForChangeNotification
RETURN 0

次に、ASP.NET 側の準備です。まず、web.config で caching 要素の設定を行います。以下の例では、SQL キャッシュ依存関係を構築するためにポーリングを有効にし、ポーリング間隔を 10 秒に設定しています。さらに、依存関係を持たせるデータベースを接続文字列によって指定し、その名前を Northwind に設定しています。

<caching>
  <sqlCacheDependency 
    enabled = "true" 
    pollTime = "10000" >
    <databases>
      <add 
        name="Northwind"
        connectionStringName="Northwind2" 
        pollTime = "10000" />
    </databases>
  </sqlCacheDependency>
</caching>

ASP.NET のキャッシュには、アプリケーションキャッシュページ出力キャッシュ およびデータソースコントロール(SqlDataSource, ObjectDataSource など)の持つキャッシュがありますが、いずれも SQL キャッシュ依存関係を構築することが可能です。

以下の例は、MDSN ライブラリの SqlCacheDependency クラス の説明に記載されていたサンプルコードに手を加えたものです。この記事の一番上の画像を表示させたものです。

Northwind データベースの Employees テーブルから DataTable を作成し、それをアプリケーションキャッシュに格納するとともに GridView で表示しています。キャッシュと SQL Server との間に依存関係を持たせ、Employees テーブルが更新されたらキャッシュを廃棄するようにしています。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="EmployeesDataSetTableAdapters" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">   

  // SqlCacheDependency クラスのサンプル。SqlDataSource 
  // をキャッシュしてもうまくいかないので DataTable を
  // キャッシュするように変更。
    
  protected void Page_Load(object sender, EventArgs e)
  {
    string dateTimeNow = DateTime.Now.ToString();
    Label1.Text = dateTimeNow;
    Label2.Text = dateTimeNow;
        
    SqlCacheDependency dependency = null;
    string connectionString =
      ConfigurationManager.
      ConnectionStrings["Northwind2"].ConnectionString;

    if (Cache["EmployeesDataTable"] == null)
    {            
      try
      {
        dependency = 
          new SqlCacheDependency("Northwind", "Employees");
      }
      catch (DatabaseNotEnabledForNotificationException)
      {
        try
        {
          SqlCacheDependencyAdmin.
            EnableNotifications(connectionString);
        }
        catch (UnauthorizedAccessException)
        {
          Response.Redirect("ErrorPage.htm");                    
        }
      }
      catch (TableNotEnabledForNotificationException)
      {
        try
        {
          SqlCacheDependencyAdmin.
            EnableTableForNotifications(
            connectionString, "Employees");
        }
        catch (SqlException)
        {
          Response.Redirect("ErrorPage.htm");
        }
      }
      finally
      {
        EmployeesTableAdapter adapter = 
          new EmployeesTableAdapter();
        EmployeesDataSet.EmployeesDataTable table = 
          adapter.GetData();
        GridView1.DataSource = table;
        GridView1.DataBind();
        Cache.Insert("EmployeesDataTable", 
          table, dependency, 
          DateTime.Now.AddMinutes(60), 
          Cache.NoSlidingExpiration);                
      }
    }
    else
    {
      GridView1.DataSource = 
        (EmployeesDataSet.EmployeesDataTable)
        Cache["EmployeesDataTable"];
      GridView1.DataBind();           
    }
  }

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>SQL Cache Dependency</title>
  <style type="text/css">
    #UpdatePanel1
    { 
      width: 250px; 
      height: 370px; 
      border: gray 1px solid;
      margin-left: 10px; 
      margin-top: 10px;
      padding: 0 5px 0 5px;
      background-color: #eeeeee;
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
    <h1>SQL Cache Dependency</h1>
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>

    <asp:Label ID="Label1" runat="server">
    </asp:Label>        

    <asp:UpdatePanel ID="UpdatePanel1" runat="server" >
      <ContentTemplate>
        <p>UpdatePanel</p>
        <hr />
        <p><asp:Label ID="Label2" runat="server">
           </asp:Label></p>  
                
        <asp:GridView ID="GridView1" 
          runat="server" 
          AutoGenerateColumns="False" 
          DataKeyNames="EmployeeID"
          EnableModelValidation="True" 
          EnableViewState="False" 
          BackColor="White">
          <Columns>
            <asp:BoundField 
              DataField="EmployeeID" 
              HeaderText="EmployeeID" 
              InsertVisible="False" 
              ReadOnly="True" 
              SortExpression="EmployeeID" />
            <asp:BoundField 
              DataField="LastName" 
              HeaderText="LastName" 
              SortExpression="LastName" />
            <asp:BoundField 
              DataField="FirstName" 
              HeaderText="FirstName" 
              SortExpression="FirstName" />
          </Columns>
        </asp:GridView>

        <asp:Button ID="Button1" 
          runat="server" 
          Text="Refresh Panel" />
 
      </ContentTemplate>
    </asp:UpdatePanel>        
  </form>
</body>
</html>

aspnet_regsql.exe を使わなくても、SqlCacheDependencyAdmin クラスのメソッドを呼び出して SQL キャッシュ依存関係を動的に設定することができるそうですが、上記にはそのためのコードが含まれています。

ただし、SQL Server データベースへのアクセスで使用するアカウント(ワーカープロセスのアカウント。IIS6, 7 のデフォルトで NETWORK SERVICE)に、テーブルおよびストアドプロシージャを作成できる権限が必要です。さらに、通知を有効にするテーブルでトリガを作成できる権限が必要です。その設定には SQL Server の管理者特権が必要で、SQL Server の設定に詳しくないと難しそうです。

Tags:

Cache

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  June 2020  >>
MoTuWeThFrSaSu
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

View posts in large calendar