WebSurfer's Home

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

CustomValidator と RequiredFieldValidator

by WebSurfer 2012年7月22日 12:21

CustomValidatorRequiredFieldValidator について新発見(?)がありましたので、忘れないように書いておきます。

(1) CustomValidator

RequiredFieldValidator 以外では、未入力に対する検証結果は OK 扱いになると思っていましたが、CustomValidator の場合は例外がありました。

それは、.NET 2.0 で追加された ValidateEmptyText プロパティ を true にすることです。

ValidateEmptyText プロパティがデフォルトの false の場合は、RegularExpressionValidator や RangeValidator などと同様に、未入力の場合はその値を評価することなく、検証結果は OK 扱いになります。

ValidateEmptyText プロパティを true にすることにより、検証対象コントロールが未入力でも、CustomValidator は、プログラマが設定した検証ロジックを使用して検証対象のコントロールの値を評価し、検証結果を返します。

では、CustomValidator の ValidateEmptyText プロパティを true にして空白の検証を可能にし、RequiredFieldValidator を使わないで済ませられるかと言えば、機能的にはそれで問題なくても、ユーザビリティの面では問題がありそうです。

検証コントロールは、ユーザーに適切なエラーメッセージを提供するということも重要な目的の一つだそうです。検証条件が異なれば適切なエラーメッセージは異なるはずです。

検証コントロールのエラーメッセージは固定的なので、CustomValidator 一つで済ませる場合、エラーメッセージは "未入力もしくは入力形式がXXXと異なります" とせざるを得ません。

それより、RequiredFieldValidator と CustomValidator を併用して、未入力の時は "未入力です" というエラーメッセージを、入力はあるが形式が違っている場合は "入力形式がXXXと異なります" とした方がユーザーフレンドリーだと思います。

というわけで、結局、未入力の検証には、RequiredFieldValidator を使用するのが正解のようです。

なお、上記で言う「未入力」とは String.Empty だけではなくて、半角/全角スペースも含まれますので注意してください。実は、これも今回調べるまで知らなかったです。(笑)

あともうひとつ。CustomValidator がデフォルトで未入力でも検証 OK とするのには理由があります。それは、RequiredFieldValidator と併用した場合、未入力の際に 2 つのエラーメッセージが同時に表示されるのはユーザーにとって煩わしいからだそうです。

(2) RequiredFieldValidator

RequiredFieldValidator は「未入力」をチェックするものだと思っていましたが、正確にはそうではなかったです。

MSDN ライブラリに書いてあったのですが、実は、"入力コントロールに対する検証は、そのコントロールがフォーカスを失ったときに、その値が InitialValue プロパティ の値から変更されていない場合は失敗になります。" ということだそうです。

従って、MSDN ライブラリのサンプルコードにあるように、検証対象の TextBox の Text プロパティに "Enter a value" と設定しておき、RequiredFieldValidator の InitialValue プロパティにも同じ文字列 "Enter a value" を設定しておけば、ユーザーが初期値を変更しなかった場合に検証 NG とします。

InitialValue プロパティのデフォルト値は String.Empty、TextBox の Text プロパティもデフォルトで String.Empty なので、結果的に「未入力」の検証が可能になるということです。

なお、全角/半角スペースを入力しても検証 NG となるのは、MSDN ライブラリにも書いてありますように、"InitialValue プロパティと入力コントロールの両方の文字列は、検証が実行される前にその文字列の前後の余分な空白が削除されてトリムされます。" からです。

Tags:

Validation

ASP.NET 4 の要求の検証

by WebSurfer 2012年2月19日 15:13

ユーザー入力に <script> のような文字を許可するために、web.config の pages 要素または個々のページの @ Page ディレクティブで ValidateRequest="false" に設定することがあると思います。

ASP.NET 3.5 以前の場合はそれだけで <script> のような文字入力を許可できましたが、ASP.NET 4 では、それだけでは以下の画面のように HttpRequestValidationException がスローされてしまいます。

HttpRequestValidationException がスローされた

ASP.NET 4 ではセキュリティ強化のため、デフォルトで、BeginRequest の前に検証を有効にしているそうです。その結果、aspx ページ要求のみでなく、ASP.NET のすべてのリソースの要求(例えば、クッキー、Web サービスやカスタム HTTP ハンドラによる要求、カスタム HTTP モジュールが読む HTTP 要求のコンテンツ)に対して検証が適用されるとのことです。

この検証を、従前(ASP.NET 3.5 以前)の方法にするか新しい(ASP.NET 4)方法にするかを指定するために、httpRuntime 要素に新しい属性 requestValidationMode が追加されています。

この requestValidationMode が 4.0(デフォルト)に設定されている場合は、web.config の pages 要素 (存在する場合) と個々のページの @ Page ディレクティブの要求検証設定は無視されます。

そのため、requestValidationMode を設定しないとデフォルトの 4.0 になって、たとえ @ Page ディレクティブで ValidateRequest="false" に設定してあってもそれは無視され、ユーザーが <script> のような文字を POST すると例外がスローされます。

詳しくは Microsoft のホワイトペーパー ASP.NET 4 Breaking Changes の ASP.NET Request Validation のセクションおよび MSDN ライブラリの RequestValidationMode プロパティ のページを参照してください。

従って、ASP.NET 4 でユーザー入力に <script> のような文字を許可するためには、httpRuntime 要素の requestValidationMode 属性を 2.0 に設定した上で、ValidateRequest="false" に設定しなければなりません。

通常はユーザー入力のある特定のページのみ requestValidationMode="2.0" に設定したいと思いますが、そのような場合は location 要素の path 属性にそのページを指定します。以下のような感じです。

<configuration>
  <location path="133-RequestValidationMode.aspx">
    <system.web>
      <httpRuntime requestValidationMode="2.0" />
    </system.web>
  </location>
</configuration>

Tags:

Validation

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

About this blog

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

Calendar

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

View posts in large calendar