WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

SQL キャッシュ依存関係

by WebSurfer 2011年3月28日 21:44

注: 以下は .NET Framework 版 ASP.NET Web Forms アプリの話です。ASP.NET Core アプリの場合は「ASP.NET Core で SQL キャッシュ依存関係」に書きましたのでそちらを見てください。

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

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

まず、キャッシュ全般に関する予備知識として、Microsoft のドキュメント ASP.NET Caching Overview(日本語のドキュメントはリンク切れ)に一通り目を通しておくことをお勧めします。

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

オプションのパラメータの設定方法詳細については ASP.NET SQL Server Registration Tool (Aspnet_regsql.exe) (日本語のドキュメントはリンク切れ)を参照してください。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 のキャッシュには、アプリケーションキャッシュ(Cache クラス 参照)、ページ出力キャッシュ(@ OutputCache 参照)およびデータソースコントロール(SqlDataSource, ObjectDataSource など)の持つキャッシュがありますが、いずれも SQL キャッシュ依存関係を構築することが可能です。

以下の例は、Microsoft のドキュメント「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

ダウンロードファイル名の文字化け

by WebSurfer 2011年3月20日 12:33

2014/3/2 改版
IE9 より RFC 6266 (RFC 2231/RFC 5987) がサポートされたということで、その点を考慮してこの記事を全面書き換えました。

ASP.NET Web アプリでファイルをダウンロードする際、AppendHeader メソッドを使って Content-Disposition ヘッダフィールドの attachment; filename= に日本語のファイル名を設定し、受信側のブラウザに IE を使用すると、ファイル名が以下の画像に示すように文字化けします。(以下の画像の例では、オリジナルのファイル名を "日本語のファイル名.xls" としています)

ダウンロードファイル名の文字化け

その理由は MSDN Blog の記事 Downloads and International Filenames をそのまま引用すると "Early versions of Internet Explorer worked around this limitation by assuming that any non-ASCII characters within HTTP headers were encoded using the local system's Windows codepage" ということだそうです。

上記の英文を具体的に説明すると次の通りです。

例えば以下のコードのよう AppendHeader メソッドを使ってファイル名を設定すると、応答ヘッダで "日本語" の部分は UTF-8 のバイト列 e6 97 a5 e6 9c ac e8 aa 9e となります。(Fiddler2 の HexView で見ると分かります。ASP.NET Web アプリではデフォルトでは応答ヘッダはすべて UTF-8 になりますので、ファイル名の部分も UTF-8 のバイト列として送信されます)

Response.AppendHeader("Content-Disposition",
      "attachment;filename=日本語.xls");

上の MSDN Blog に書いてあるように、応答ヘッダに ASCII コード以外の文字(上の例では e6 97 a5 e6 9c ac e8 aa 9e)がある場合、その部分は自分の PC の Windows codepage で、即ち日本語 OS の場合は Shift_JIS として解釈されます。

IE は e6 97 a5 e6 9c ac e8 aa 9e を Shift_JIS コードとして解釈しようとするので、正しくデコードできず文字化けするというわけです。(ちなみに、Shift_JIS では "日本語" は 93 fa 96 7b 8c ea になります)

解決策は、マイクロソフト サポートオンラインの記事 ファイルをダウンロードする ASP.NET ページで日本語ファイル名が文字化けする に以下の 3 つの方法が紹介されています。

  1. HttpServerUtility.UrlEncode メソッドを使ってファイル名をエンコードする。
  2. HttpResponse.HeaderEncoding プロパティを使って Shift_JIS のヘッダを送信する。
  3. RFC 6266 に準拠して attachment; filename*= UTF-8''%e2%82%ac%20rates のようにする。

1 番目の方法は、一部のブラウザが対応していないという点が問題です。IE, Firefox, Chrome, Safari, Opera で検証した結果は以下の表の通りでした。

ブラウザ UrlEncode
あり なし
IE OK NG
文字化けする
Firefox NG
エンコードされた文字になる
OK
Chrome OK OK
Safari NG
エンコードされた文字になる
OK
Opera OK OK

上記の結果は、最初にこの記事を書いた 2011 年 3 月 20 日の時点で検証に使ったブラウザ IE8, Firefox 3.6.15, Chrome 10.0.648.151, Safari 5.0.4, Opera 11.01 でも、この記事を改版した 2014 年 3 月 2 日の時点で検証に使ったブラウザ IE9, Firefox 27.0.1, Chrome 33.0.1750.117, Safari 5.1.7, Opera 12.16 でも同じでした。

Firefox と Safari の場合は、UrlEncode するのはダメで、%e6%97%a5%e6%9c%ac%e8%aa%9e.xls のようなエンコードされた名前になってしまいます。Chrome と Opera は UrlEncode 有り/無しどちらも OK でした。

また、IE の場合でも、ファイル名に半角空白が含まれているとうまく行きません。UrlEncode メソッドは、半角空白を + に変換するので、例えば "file name.txt"(file と name の間に半角空白)" は "file+name.txt" になってしまいます。なので、半角空白は URL エンコードせず、そのままにしておく必要があります。

半角空白を + ではなく %20 に変換(パーセントエンコーディング)しても問題があります。IE 側で開く「ファイルのダウンロード」ダイアログで[開く(O)]をクリックするとメモ帳が開いて中身が表示されますが(もちろん拡張子 txt がメモ帳に関連付けられているとして)、メモ帳のウィンドウの左上に表示されるファイル名の空白は %20 のままになります。(上記の例では "file%20name.txt" となる) まぁ、これは大した問題ではないのかもしれませんが、気分がよくないです。(笑)

2 番目の方法(Shift_JIS のヘッダを送信)は IE ではうまく行きますが、当然、IE 以外のブラウザは対応できません。

また、ファイル名を Shift_JIS にすると、IE 自体は対応できても、それ以前にプロキシサーバーで文字化けして、結局、文字化けは回避できないという可能性があります。それに、単にファイル名が文字化けするだけでなく、他に予期しない好ましからざる副作用が出る恐れもあります。従って、2 番目の方法は、使用するブラウザが IE に限定される場合でも避けた方が賢明だと思います。

3 番目の方法は RFC 6266 をサポートしていないブラウザ(IE8-、Safari 5.1.7 など)には使えないという問題があります。また、これも、UrlEncode メソッドで、半角空白が + に変換されることにより、"file name.txt"(file と name の間に半角空白)" が "file+name.txt" になってしまうという問題があります。半角空白はパーセントエンコーディング("+" ではなくて "%20" に変換)しないとうまく行きません。

結局、現状ではファイル名には ASCII 文字以外は使わないというのが一番の解決策だと思います。

どうしても日本語のファイル名を使って、かつ IE, Firefox, Chrome 等の複数のブラウザに対応するなら、以下のようにブラウザによって応答ヘッダを切り替える他方法はなさそうです。

(注)別の記事 ダウンロードは HTTP ハンドラで で書きましたように、ファイルなどをダウンロードするのに .aspx ページを利用するといろいろ問題がありそうなので、以下のサンプルコードでは HTTP ハンドラを使用しています。

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

using System;
using System.Web;

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

    string fileName = "日 本 語 (+japanese+).txt";

    // + は %2b に変換され、空白は + に変換される。
    string encodedFileName = context.Server.UrlEncode(fileName); 
    encodedFileName = encodedFileName.Replace("+", "%20");
        
    response.Clear();

    response.ContentType = "text/plain";
        
    // キャッシュを許可しない
    response.Cache.SetCacheability(HttpCacheability.NoCache);
    response.Cache.SetExpires(DateTime.Now.ToUniversalTime());
    response.Cache.SetMaxAge(new TimeSpan(0, 0, 0, 0));

    HttpBrowserCapabilities browser = request.Browser;

    // IE の場合。
    // IE11 では Browser プロパティは "Mozilla" (.NET 2.0) 
    // または "InternetExplorer" (.NET 4) になる。IE の場合
    // User Agent には必ず "Trident" と言う文字列が入ってい
    // るらしいので、そちらで判定した方がよさそう。
    if (browser.Browser.ToUpper().IndexOf("IE") >= 0 ||
        request.UserAgent.Contains("Trident"))            
    {
      // IE8 以下(RFC 6266 未サポート)
      // 空白を %20 のままにしておくと処理がうまくいかない。
      // 「ファイルのダウンロード」ダイアログで[開く(O)]を
      // クリックするとメモ帳が開いて中身が表示されるが %20 
      // がそのままウィンドウの左上に表示されるファイル名に
      // 含まれる。それが気になる場合は、以下のように %20 を
      // 空白に置き換える。(注:「ファイルのダウンロード」
      // ダイアログで[保存(S)]をクリックすれば何故か %20 
      // は空白に変換されて保存される)
      if (browser.MajorVersion < 9)
      {
        response.AppendHeader("Content-Disposition", 
            "attachment;filename=\"" + 
            encodedFileName.Replace("%20", " ") +  "\"");
      }
      // IE9 以上(RFC 6266 サポート)
      else
      {
        response.AppendHeader("Content-Disposition", 
            "attachment;filename*=utf-8''" + encodedFileName);
      }
    }
    // IE 以外
    else
    {
      // Safari 5.1.7 がまだ RFC 6266 未対応らしいので 
      // filename="xxxxx" の併記が必要。 
      response.AppendHeader("Content-Disposition", 
          "attachment;filename=\"" + fileName + 
          "\";filename*=utf-8''" + encodedFileName);
    }

    response.Write("こんにちは世界!");
  }
 
  public bool IsReusable 
  {
    get 
    {
      return false;
    }
  }
}

なお、IIS7 で試した限りですが(他のサーバーは不明)、以下のコードのように HyperLink で直リンクすれば、どのブラウザでも文字化けはありませんでした。

<asp:HyperLink 
    ID="HyperLink2" 
    runat="server" 
    NavigateUrl="~/日 本 語 (japanese).zip">
    日 本 語 (japanese).zip
</asp:HyperLink>

その理由は、ブラウザから GET 要求する url のファイル名はパーセントエンコーディング(半角空白は "+" ではなく "%20")され、応答ヘッダには以下のように正しく Content-Type が指定されると共に Content-Disposition フィールドが含まれないからです。

HTTP/1.1 200 OK
Content-Type: application/x-zip-compressed
Last-Modified: Fri, 21 Feb 2014 14:39:36 GMT
Accept-Ranges: bytes
ETag: "752e46be122fcf1:0"
Server: Microsoft-IIS/7.0
X-Powered-By: ASP.NET
Date: Sun, 02 Mar 2014 07:42:01 GMT
Content-Length: 1956

なお、直リンクする場合、NavigateUrl に指定するファイル名に "+" を入れると、IIS7 では HTTP エラー 404.11(ダブルエスケープシーケンスを含む要求の拒否)になるので注意してください。

Tags:

Upload Download

Lightbox Plugin

by WebSurfer 2011年2月20日 18:21

jQuery 版の Lightbox plugin を実装してみました。以下の画像はダウンロードしたサンプルに含まれていたもので、サンプルと同様に、次の画像、前の画像を連続して表示できます。

gallery という id を持つ div タグの中に、a タグで囲った img 要素を 5 個並べて表示しています。jQuery のセレクタ $('#gallery a') で gallery の中にある a タグを探し、それに対して lightBox() というスクリプトで href 属性に指定した画像ファイルを Lightbox 中に表示するようにしています。


湘南

左の画像は、上の gallery という id を持つ div タグの外にあり、単独で表示される(上の画像と連続して表示されない)ようにしたものです。

a タグで rel="lightbox"(任意で可)として、$('a[rel^=lightbox]') という属性セレクタで rel 要素に lightbox という文字列を含む a タグを探し、それに対して lightBox() というスクリプトで href 属性に指定した画像ファイルを Lightbox 中に表示するようにしています。

BlogEngine.NET へのスクリプトファイルと css ファイルの指定の追加は、以下の Extension を用いました。マスターページに直接書き込む方が簡単ですが、その場合はすべてのテーマのマスターページに書かなければならず、スマートではないので。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using BlogEngine.Core.Web.Controls;
using BlogEngine.Core;
using System.Web.UI.HtmlControls;

[Extension("Enable jQuery Lightbox plugin 0.5", 
"1.0", 
"<a target=\"_blank\" href=\"http://surferonwww.info/Default.aspx\">WebSurfer</a>")]
public class Lightbox
{
  private string extensionName = "LightboxPlugin";
  private static string scriptsFolder = "Scripts";
  private static string jQueryFilename = "jquery-1.4.1.min.js";
  private static string lightboxScriptFilename = 
    "jquery.lightbox-0.5.pack.js";
  private static string stylesFolder = "css";
  private static string cssFilename = "jquery.lightbox-0.5.css";
    
  public Lightbox()
  {
    BlogEngine.Core.Post.Serving += 
      new EventHandler<ServingEventArgs>(EnableLightbox);
  }

  private void EnableLightbox(object sender, ServingEventArgs e)
  {
    HttpContext context = HttpContext.Current;
    if (context.CurrentHandler is System.Web.UI.Page)
    {
      if (context.Items[extensionName] == null)
      {
        System.Web.UI.Page page = 
          (System.Web.UI.Page)context.CurrentHandler;

        page.Header.Controls.Add(JavaScriptInclude(scriptsFolder + 
          "/" + jQueryFilename));
        page.Header.Controls.Add(JavaScriptInclude(scriptsFolder + 
          "/" + lightboxScriptFilename));

        HtmlLink lightboxCss = new HtmlLink();
        lightboxCss.Attributes["type"] = "text/css";
        lightboxCss.Attributes["rel"] = "stylesheet";
        lightboxCss.Attributes["href"] = 
          Utils.RelativeWebRoot + stylesFolder + "/" + cssFilename;
        page.Header.Controls.Add(lightboxCss);

        context.Items[extensionName] = 1;
      }
    }
  }

  #region Private helper methods

  private HtmlGenericControl JavaScriptInclude(string url)
  {
    HtmlGenericControl script = new HtmlGenericControl("script");
    script.Attributes["type"] = "text/javascript";
    script.Attributes["src"] = ResolveScriptUrl(url);
    return script;
  }

  private string ResolveScriptUrl(string url)
  {
    return Utils.RelativeWebRoot + "js.axd?path=" 
      + Utils.RelativeWebRoot + url; 
  }

  #endregion
}

jQuery のスクリプトは、ブログの中に直接書き込んでいます。今回の場合は以下の通りです。なぜかと言えば、今回の例で使った gallery や lightbox という名前を、ブログの記事を書くときに自由に指定したかったからです。

<script type="text/javascript">
//<![CDATA[
$(function () {
  $('#gallery a').lightBox({
    imageLoading: '/BlogEngine/Images/lightbox-ico-loading.gif',
    imageBtnClose: '/BlogEngine/Images/lightbox-btn-close.gif',
    imageBtnPrev: '/BlogEngine/Images/lightbox-btn-prev.gif',
    imageBtnNext: '/BlogEngine/Images/lightbox-btn-next.gif',
    imageBlank: '/BlogEngine/Images/lightbox-blank.gif'
  });
  $('a[rel^=lightbox]').lightBox({
    imageLoading: '/BlogEngine/Images/lightbox-ico-loading.gif',
    imageBtnClose: '/BlogEngine/Images/lightbox-btn-close.gif',
    imageBtnPrev: '/BlogEngine/Images/lightbox-btn-prev.gif',
    imageBtnNext: '/BlogEngine/Images/lightbox-btn-next.gif',
    imageBlank: '/BlogEngine/Images/lightbox-blank.gif'
  }); 
});
//]]>
</script>

Tags: ,

BlogEngine.NET

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar