WebSurfer's Home

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

HTTP ハンドラで Session を読み書き

by WebSurfer 2012年1月17日 22:27

HTTP ハンドラ内では Session の読み書きができません。例えば、以下のようにすると context.Session は null になって NullReferenceException がスローされてしまいます。

string s = (string)context.Session["note"];

ご存知でしたでしょうか? 実は、自分はそのことを知らなくて、つい先日 1 時間ぐらいハマってしまいました。(笑)

この問題を解決するには、HTTP ハンドラのクラスにマーカーインターフェースを継承させ、この HTTP ハンドラは Session へのアクセスを必要としているという印を付けてやります。

そのマーカーインターフェイスは System.Web.SessionState 名前空間 に属しており、以下のとおり 2 種類あります。

  1. IRequiresSessionState 読み取り/書き込みアクセス権を必要とすることを指定します。
  2. IReadOnlySessionState 読み取り専用アクセス権のみが必要であることを指定します。

実際の使用例は、まぁ、書かなくても分るとは思いますが、以下のような感じです(この例では IRequiresSessionState を使用)。

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

using System;
using System.Web;
using System.Web.SessionState;

public class _MyHttpHandler : IHttpHandler, IRequiresSessionState 
{    
  public void ProcessRequest (HttpContext context) 
  {
    byte[] data = (byte[])context.Session["PageResponse"];
        
    if (data != null)
    {
      context.Response.BinaryWrite(data);
      context.Session.Remove("PageResponse");
    }
    context.Response.End();
  }
 
  // 以下省略

マーカーインターフェイスはメソッドの定義を持たないので、一般的なインターフェイスを継承したときのように、継承したクラス内にメソッドを実装する必要はありません。

Tags: ,

ASP.NET

EnableEventValidation

by WebSurfer 2012年1月15日 15:19

インジェクションアタックに対するセキュリティ対策のため、ASP.NET 2.0 からコントロールにイベント検証の機能が追加されています。ポストバック処理の際、クライアントから POST された値を検証し、未知の値の場合は以下のように ArgumentException をスローします。

無効なポストバックまたはコールバック引数です。イベントの検証は、構成の <pages enableEventValidation="true"/>、またはページの <%@ Page EnableEventValidation="true" %> を使用して有効にされます。セキュリティの目的により、この機能は、イベントをポストバックまたはコールバックする引数が、それらを最初に表示したサーバー コントロールから発行されていることを確認します。データが有効であり、予期されている場合、検証のためのポストバックまたはコールバック データを登録するために ClientScriptManager.RegisterForEventValidation メソッドを使用してください。

ASP.NET のすべてのイベント ドリブン コントロールは、既定でこの機能を使用するそうです。実は、普通にコントロールを使用しているときは当然検証は通るので、このような機能があることは知らなかったです。(汗)

例えば以下のコードのように、クライアントスクリプトで DropDownList に ListItem(HTML では option)を追加し、追加した項目(以下のコードの例では Item-3)を選んでからポストバックすると、上記の例外がスローされるのが分かります。

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

<!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>EnableEventValidation</title>
  <script type="text/javascript">
  //<![CDATA[
    function AddItemToList() {
      var d = document.getElementById('<%=ddl.ClientID%>');
      d.options[2] = new Option('Item-3', '3');
    }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:DropDownList ID="ddl" runat="server">
      <asp:ListItem Value="1">Item-1</asp:ListItem>
      <asp:ListItem Value="2">Item-2</asp:ListItem>
    </asp:DropDownList>
    <input type="button" 
      id="btn1" 
      value="Add Item-3" 
      onclick="javascript:AddItemToList();" />
    <br />
    <asp:Button ID="Button1" 
      runat="server" 
      Text="PostBack" />
  </div>
  </form>
</body>
</html>

どのように検証しているかと言うと、ASP.NET はコントロールの UniqueID とポストされる可能性のある値を以下のように隠しフィールドに格納し、ポストバックされた際に、ポストされた値と隠しフィールドの値を比較するという操作を行っています。上の例では、value の "1" と "2" は隠しフィールドにありますが、"3" はないので検証 NG となって例外がスローされます。

<input type="hidden" 
  name="__EVENTVALIDATION" 
  id="__EVENTVALIDATION"
  value="/wEWBALBxKbEBwLVoIS......." />

この例外を避けるには、以下の方法があります。

  1. @ Page ディレクティブに EnableEventValidation="false" を追加する。
  2. ClientScriptManager.RegisterForEventValidation メソッドで検証用のイベント参照を登録する。

上記 1 の方法では、ページ全体でイベント検証が無効になってしまいますので、そのページで意図しない影響を与える可能性があるポストバックが一切発生しないことが条件になります。

上記 2 の方法では、イベント検証をパスするように、あらかじめイベント参照(上記のコードの例では DropDownList の UniqueID と Item-3 の value である "3")を登録します。

RegisterForEventValidation メソッドはレンダリングのタイミングで呼ぶ必要があるそうです。従って、Render メソッドを override してその中で設定します。

今回の例の場合、Page の Render メソッドを override するより、DropDownList を継承したカスタムコントロールを作って、その Render メソッドを override した方がよさそうです。以下のような感じです。

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Web;
using System.Web.UI.WebControls;
using System.Web.UI;

namespace EventValidationTest
{
  // カスタムコントロールは、SupportsEventValidationAttribute
  // 属性を定義しないと、イベントの検証に含まれません。
  [SupportsEventValidation]
  public class MyDropDownList : DropDownList
  {
    // RegisterForEventValidation メソッドを使用して、
    // UniqueID と POST される value を登録する(具体的に
    // は、隠しフィールド __EVENTVALIDATION の value に追
    // 加する)。
    protected override void Render(HtmlTextWriter writer)
    {
      Page.ClientScript.RegisterForEventValidation(
        this.UniqueID,
        "3"
      );
      base.Render(writer);
    }

    // 以下はオマケ。
    // クライアントスクリプトで、DropDownList に動的に追加し
    // た ListItem (option) は、ポストバック後に再描画された
    // とき消えてしまう。以下のコードで再生できる。
    protected override bool LoadPostData(string postDataKey,
      NameValueCollection postCollection)
    {
      string postedValue = postCollection[postDataKey];
      if (postedValue != null &&
        this.Items.FindByValue(postedValue) == null)
      {
        this.Items.Add(
          new ListItem("Item-" + postedValue, postedValue));
      }
      return base.LoadPostData(postDataKey, postCollection);
    }
  }
}

ただし、上記 2 の対応が常に可能ではないところが問題です。この例では value があらかじめ分かってないと登録できません。value が確定できない場合は、イベント検証を無効にするより手がありません。しかし、ページ全体のイベント検証を無効にしたくはないですよね。

問題のコントロールのみイベント検証を無効にするには、そのコントロールを継承したカスタムコントロールを作り、SupportsEventValidationAttribute 属性を定義しないことで実現できます。

なお、イベント検証は、ポストバックの時のみでなく、クライアントコールバックの時でも有効にできます。詳しくは MSDN ライブラリの ValidateEvent メソッド を見てください。

Tags:

Validation

textarea のキャレット位置に文字列を挿入

by WebSurfer 2012年1月9日 23:04

JavaScript を使って textarea のキャレット(カーソル)位置に文字列を挿入するサンプルです。

textarea のキャレット位置に文字列を挿入

挿入する文字列を入力するために jQuery UI の dialog を利用していますが、それ以外は JavaScript のみを使用しています。

Firefox などの場合は、選択された部分の先頭、末尾の index (整数型)を、それぞれ selectionStart、selectionEnd で取得できますが、IE の場合はそれに該当するプロパティがないのが問題です。

IE の場合は、document.selection プロパティで selection オブジェクト(textarea 要素そのものではなく、textarea 要素の中の選択された文字の部分)を取得し、さらに createRange メソッドを使って TextRange オブジェクトを作成し、それを操作することになります。

TextRange オブジェクトについては、MSDN ライブラリの TextRangeオブジェクトの使用 を参照してください。

そのサンプルコードは以下の通りです。上の画像は、このコードを実行したときのブラウザの画面です。実際に動かして試せるよう 実験室 にアップしましたので、興味のある方は試してみてください。

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

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>textarea のキャレット位置に文字列を挿入</title>
  <script src="Scripts/jquery-1.6.2.min.js" type="text/javascript"></script>
  <script src="Scripts/jquery-ui-1.8.16.custom.min.js" type="text/javascript"></script>
  <link href="css/smoothness/jquery-ui-1.8.16.custom.css" rel="stylesheet" type="text/css" />
  <script type="text/javascript">
  //<![CDATA[

    // IE の場合、[ダイアログ表示]ボタンクリックの時点で
    // textarea 内の選択された文字部分の TextRange オブジェ
    // クトを取得して維持しておく。そうしないと、textbox に
    // 入力中に textarea からフォーカスが外れ、選択範囲がリ
    // セットされてしまう。
    // Firefox 等は textarea からフォーカスが外れても、選択
    // 範囲は維持されるので、このような処置は不要。
    var textRange = null;

    function getTextRange(textarea) {
      // textarea の文字が選択されてない場合はフォーカスを
      // 当てないとうまくいかない。
      textarea.focus();

      // document.selection プロパティで selection オブジ
      // ェクト(testarea の中の選択された文字の部分)を取
      // 得する。文字が選択されていない場合、キャレット位
      // 置の空の selection オブジェクトになる。
      // selection オブジェクトに何か処理を行う場合は、
      // createRange メソッドで TextRange オブジェクトを作
      // 成して処理する。
      return document.selection.createRange();
    }

    function insertText(textarea, txt) {
      if (textRange == null) {
        // IE 以外の場合

        // 選択部分の先頭の index と長さを取得
        var index = textarea.selectionStart;
        var length = textarea.selectionEnd - index;

        // 文字列を挿入
        textarea.value = textarea.value.substr(0, index) +
          txt + textarea.value.substr(index + length);

        // キャレット位置を挿入した文字列の最後尾に移動
        textarea.focus();
        var newCaretPosition = index + txt.length;
        textarea.setSelectionRange(
          newCaretPosition, newCaretPosition);
      } else {
        // IE の場合

        // TextRange.text プロパティにダイアログの文字列を
        // 設定し、testarea の中の選択された文字部分を置き
        // 換える。
        textRange.text = txt;

        // キャレット位置を挿入した文字列の最後尾に移動
        textRange.select();
      }
    }

    // ---------------------------------------------------
    // 挿入する文字列の入力用に jQuery UI の dialog を利用
    $(function () {
      $("#dialog").dialog({
        bgiframe: true,
        autoOpen: false,
        modal: true,
        resizable: false,
        buttons: {
          'Insert': function () {
            insertText(
              $('#textarea').get(0),
              $('#textbox').val()
            );
            $(this).dialog('close');
          },
          'Cancel': function () {
            $(this).dialog('close');
          }
        },
        open: function () {
          $('#textbox').val('')
        }
      });
      $('#buttonShowDialog').click(function () {
        if (document.selection != undefined) {
          textRange = getTextRange($('#textarea').get(0));
        }
        $('#dialog').dialog('open');
      });
    });
  //]]>
  </script>
</head>
<body style="font-size: 12px;">
  <h1>キャレット位置に文字列を挿入</h1>
  <textarea id="textarea" cols="50" rows="5">textarea のキャレット位置に文字列を挿入します。</textarea><br />
  <button id="buttonShowDialog">ダイアログ表示</button>

  <div id="dialog" title="挿入する文字列">
    <p>ここに入力:<br />
    <input type="text" id="textbox" size="30" /></p>
  </div>
</body>
</html>

textarea の文字列が選択されている場合、その文字列を dialog に入力した文字列で置き換えます。文字列を選択していない場合(キャレットのみの場合)、キャレットの位置に dialog に入力した文字列を挿入します。

IE9, Firefox 9.0.1, Safari 5.1.2, Opera 11.60 で検証して期待通りに動くことを確認しました。(その他のブラウザは検証してないです)

Tags: ,

JavaScript

About this blog

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

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar