WebSurfer's Home

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

@OutputCache ディレクティブ

by WebSurfer 2012年4月28日 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

__doPostBack を使ってはいけません

by WebSurfer 2012年4月21日 13:12

プログラマが、ASP.NET が自動生成する __doPostBack という名前の JavaScript 関数を、直接自分のコードに使ってはいけないという話です。

GridView で[選択]ボタンを使わないでポストバックをかけ、SelectedIndexChanged イベントで選択された行を取得

ASP.NET には、クライアントのイベントでサーバーにデータをポストバックするための、クライアントスクリプトを自動生成する機能があります。

それが表題の __doPostBack という名前の JavaScript の関数です。

例えば、GridView で、AutoGenerateSelectButton プロパティを true に設定すると[選択]ボタンが表示されますが、そのハイパーリンクに __doPostBack 関数が以下のように自動的に設定されます(ASP.NET 4 では ' はエスケープされて &#39; になります)。

<td>
  <a href="javascript:__doPostBack('GridView1','Select$0')">
    選択
  </a>
</td>

引数は、前者がデータを受け取って処置をするサーバーコントロールの UniqueID(上の例では GridView1)、後者がイベントデータ(上の例では Select$0 ⇒ 行インデックス 0 が Select されたという意味)になります。

__doPostBack 関数は、この 2 ��の引数をそれぞれ __EVENTTARGET と __EVENTARGUMENT という id を持つ隠しフィールドに格納してから form を submit します。

この __doPostBack 関数や隠しフィールドは公開されてないので、将来なにもアナウンスされず変更される可能性があります。なので、プログラマがこの関数や隠しフィールドを使ってコーディングするのは避けた方がよさそうです。

代わりに、GetPostBackEventReference メソッドRaisePostBackEvent メソッド を使うのがお勧めです。

その例は、このブログの先の記事 CheckBox 付き Calendar コントロール でも紹介していますが、ここでも GridView を使って一例を紹介します。

上の画像は、GridView で[選択]ボタンを使わないで、行クリックでポストバックをかけ、SelectedIndexChanged イベントで選択された行を取得するサンプルです。__doPostBack 関数を直接使わないで、GetPostBackEventReference メソッドと RaisePostBackEvent メソッドを使って実現しています。

そのコードは以下の通りです。解説は、コードの中にコメントで書きましたので、そちらを参考にしてください。手抜きですみません。(笑)

実際に動かして試せるよう 実験室 にアップしましたので、興味のある方は試してみてください。

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

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

<script runat="server">

  // GridView で[選択]ボタンを使わないでポストバックをかけて
  // SelectedIndexChanged イベントで選択された行を取得。
  // ASP.NET AJAX の UpdatePanel を使っているのはオマケ。
    

  // 選択された行をチェックして SelectedIndexChanged イベント
  // を発生させるため RaisePostBackEvent メソッドを override 
  // する。そのため GridView を継承したカスタムコントロールを
  // 作る。
  public class MyGridView : GridView
  {
    // GetPostBackEventReference メソッドで設定したクライアント
    // スクリプト(この例では、下の myGridView_RowCreated イベ
    // ントハンドラで設定している)が起動されると、ポストバッ
    // クがかかり、サーバー側で RaisePostBackEvent メソッドが
    // 起動される。引数 arg には、クライアントスクリプトで設定
    // した GridView の行インデックスが渡される。
    protected override void RaisePostBackEvent(string arg)
    {
      int index;
      bool result = Int32.TryParse(arg, out index);

      if (result)
      {
        if (this.SelectedIndex != index)
        {
          this.SelectedIndex = index;
          this.OnSelectedIndexChanged(EventArgs.Empty);
        }
      }

      base.RaisePostBackEvent(arg);
    }        
  }
    
  // データソース用の DataTable を作成
  protected DataTable CreateDataTable()
  {
    DataTable dt = new DataTable();
    DataRow dr;

    dt.Columns.Add(new DataColumn("ID", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Name", typeof(string)));
    dt.Columns.Add(new DataColumn("Price", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Qty", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Amount", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Remarks", typeof(string)));

    for (int i = 0; i < 5; i++)
    {
      dr = dt.NewRow();
      dr["ID"] = i;
      dr["Name"] = "Item " + i.ToString();
      dr["Price"] = 123000 * (i + 1);
      dr["Qty"] = i + 1;
      dr["Amount"] = 123000 * (i + 1) * (i + 1);
      dr["Remarks"] = "Remarks " + i.ToString();
      dt.Rows.Add(dr);
    }
    return dt;
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    MyGridView myGridView = new MyGridView();
    myGridView.ID = "myGridView1";
    myGridView.RowCreated += 
      new GridViewRowEventHandler(myGridView_RowCreated);
    myGridView.SelectedIndexChanged += 
      new EventHandler(myGridView_SelectedIndexChanged);
    TableItemStyle tableStyle = myGridView.SelectedRowStyle;
    tableStyle.BackColor = System.Drawing.Color.Blue;
    tableStyle.ForeColor = System.Drawing.Color.White;
        
    PlaceHolder1.Controls.Add(myGridView);
        
    if (!IsPostBack)
    {
      myGridView.DataSource = CreateDataTable();
      myGridView.DataBind();
    }
  }

  protected void myGridView_RowCreated(
    object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      // __doPostBack("<UniqueID>", "<RowIndex>") という
      // クライアントスクリプトの文字列が生成される。
      string script =
        Page.ClientScript.GetPostBackEventReference(
          (GridView)sender,
          e.Row.RowIndex.ToString());
            
      // データ行の tr 要素の onclick 属性に、上で生成
      // したスクリプトを設定する。
      e.Row.Attributes["onclick"] = script;
            
      // カーソルを指型にする
      e.Row.Style["cursor"] = "pointer";
    }
  }

  protected void myGridView_SelectedIndexChanged(
    object sender, EventArgs e)
  {
    Label1.Text = "SelectedIndex: " + 
      ((GridView)sender).SelectedIndex.ToString();
  }
   
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager1" runat="server">
  </asp:ScriptManager>
  <asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
      <asp:PlaceHolder ID="PlaceHolder1" 
        runat="server">
      </asp:PlaceHolder>
      <asp:Label ID="Label1" runat="server">
      </asp:Label>
    </ContentTemplate>
  </asp:UpdatePanel>
  </form>
</body>
</html>

Tags:

ASP.NET

ACT の AutoComplete

by WebSurfer 2012年4月14日 15:48

AJAX Control Toolkit (ACT) の AutoComplete を使ってみました。

AJAX Control Toolkit の AutoComplete

実装は特に難しいことはなく、アットマーク・アイティの AutoComplete コントロールで Google サジェスト風なオートコンプリート機能を実装するには? を参考にすれば機能的には問題なく動くと思います。

ただし、スタイルを何も設定しないと、TextBox と候補リスト(AutoCompleteExtender が生成する ul, li 要素を使ったリスト)の間にスペースができてしまいます。理由は、ul 要素の Margin のトップが何故か 40px になっているからです。(IE9 の開発者ツールで「レイアウト」を見ると分かります)

スペースのできるのが気に入らない場合は、CompletionListCssClass プロパティに自分でスタイルを定義する必要があります。以下に、その例を書いておきます。

Web Form(.aspx)

CompletionListCssClass プロパティに設定するスタイルは、margin: 0px !important; とするだけではダメで、以下のように他の要素も定義しなおす必要があります。

下の例で、CompletionListItemCssClass と CompletionListHighlightedItemCssClass も設定してありますが、TextBox と候補リストの間のスペースの調整には必要ないです(オマケです)。

<%@ Page Language="C#" %>

<%@ Register Assembly="AjaxControlToolkit" 
    Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

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

<script runat="server">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <style type="text/css">
    .autocomplete
    {
        margin: 0px !important;
        padding: 0px !important;
        text-align: left;
        color: WindowText;
        overflow: auto;
        border-color: ButtonShadow;
        border-width: 1px;
        border-style: solid;
        list-style-type: none;
        background-color: inherit;
        height: 200px; 
    }

    .autocomplete_item
    {
        color: WindowText;
        background-color: Window;
        padding: 1px;
    }

    .autocomplete_highlightedItem
    {
        color: Black;
        background-color: rgb(255, 255, 153);
        padding: 1px;
    }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <ajaxToolkit:ToolkitScriptManager 
        ID="ToolkitScriptManager1" 
        runat="server">
    </ajaxToolkit:ToolkitScriptManager>
    <asp:TextBox ID="TextBox1" 
        runat="server" Columns="50">
    </asp:TextBox>
    <ajaxToolkit:AutoCompleteExtender 
        ID="TextBox1_AutoCompleteExtender" 
        runat="server"
        MinimumPrefixLength="2" 
        ServicePath="~/142-ACTAutoComplete.asmx" 
        TargetControlID="TextBox1"
        ServiceMethod="GetList"
        CompletionSetCount="20"
        CompletionListCssClass="autocomplete"
        CompletionListItemCssClass="autocomplete_item" 
        CompletionListHighlightedItemCssClass=
            "autocomplete_highlightedItem">
    </ajaxToolkit:AutoCompleteExtender>
    </form>
</body>
</html>

Web サービス(.asmx)

Web サービスは、クライアントスクリプトから呼び出して、JSON 形式のデータを返すように、クラスに ScriptService 属性を追加するところがポイントです。

以下の例で、データベースは Microsoft が無償で提供している Northwind サンプルデータベースの Products テーブルを使用しています。

<%@ WebService Language="C#" Class="_142_ACTAutoComplete" %>

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Collections.Generic;
using System.Web.Configuration;
using System.Data;
using System.Data.SqlClient;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class _142_ACTAutoComplete  : WebService 
{
  [WebMethod]
  public string[] GetList(string prefixText, int count)
  {
    List<string> list = new List<string>();
    string connString = 
      WebConfigurationManager.
        ConnectionStrings["Northwind"].ConnectionString;
    string query = 
      String.Format(
        "SELECT TOP {0} ProductName FROM Products " + 
        "WHERE ProductName LIKE @ProductName", count);
    SqlConnection connection = new SqlConnection(connString);
    SqlCommand command = new SqlCommand(query, connection);
    SqlParameter param = 
      new SqlParameter("@ProductName", SqlDbType.NVarChar, 40);
    param.Value = "%" + prefixText + "%";
    command.Parameters.Add(param);

    try
    {
      connection.Open();
      SqlDataReader reader = command.ExecuteReader();

      while (reader.Read())
      {
        list.Add(reader.GetString(0));
      }
    }
    finally
    {
      connection.Close();
    } 

    return list.ToArray();
  }    
}

Tags: , ,

AJAX

About this blog

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

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar