WebSurfer's Home

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

CustomValidator のクライアント側での検証

by WebSurfer 2020年6月27日 12:03

ASP.NET Web Forms アプリでユーザー入力の検証に用いられる CustomValidator のクライアント側での検証について調べたことをまとめて備忘録として書いておきます。

CustomValidator のクライアント側での検証

CustomValidator は、プログラマが独自の検証ロジックをコーディングして検証メソッドとしてページに実装し、ユーザー入力の検証を行うために利用されます。複数の入力コントロールにまたがって検証を行うことも可能です。

また、RegularExpressionValidator などと異なり、TextBox の他に CheckBox, RadioButton, DropDownList, FileUpload などのユーザー入力コントロールの検証に利用できます。

クライアント側での検証は、JavaScript を使って検証用メソッドを自力でコーディングし、それをページに実装することで可能になります(サーバー側でなければ検証できない場合は話は別です。ajax を使う手はいろいろ問題がありそうです。詳しくは先の記事「CustomValidator で jQuery.ajax 利用」を見てください)。

この記事の下の方に TextBox, CheckBox, RadioButton, DropDownList, FileUpload を対象として、CustomValidator によるクライアント側での検証を実装したサンプルコードを書いておきます。上の画像を表示したものです。

自分的に注意が必要と思う点を以下に箇条書きにしておきます。

  1. クライアント側での検証は html 要素の change イベントでかかるようになっています。CustomValidator を change イベントで動くようにするには ControlToValidate プロパティの設定が必要です。(注: submit でも検証がかかります。というか、change で検証がかかるのはユーザビリティ向上のためで、submit 時の検証がメインです)
  2. CheckBox, RadioButton コントロールに対しては CustomValidator の ControlToValidate プロパティを設定できません。設定すると HttpException がスローされ、例えば CheckBox の場合は「'CustomValidator' の ControlToValidate プロパティで参照されたコントロール 'CheckBox' を検証できません。」というエラーメッセージが表示されます。

    エラーとなる直接の理由は、ASP.NET 内部で CheckControlValidationProperty メソッドによる検証対象コントロールのチェックを行っていますが、CheckBox RadioButton コントロールには ValidationPropertyAttribute 属性が付与されてないためないためです。

    そもそもの理由は、Microsoft のドキュメントによると「ControlToValidate プロパティを設定せずに CustomValidator コントロールを使用することもできます。 これは、複数の入力コントロールを検証する場合や、CheckBox コントロールなどの検証コントロールで使用できない入力コントロールを検証する場合に一般的に行われます」とのことで、もともと CheckBox や RadioButton は検証コントロールを使う対象外のように読めます。
  3. TextBox, DropDownList, FileUpload コントロールについては、CustomValidator の ControlToValidate プロパティを検証対象コントロールの ID に設定すれば change イベントで検証がかかります。

    なお、ControlToValidate プロパティを設定しなくても submit で検証はかかりますので、change イベントでいちいち検証がかかるのは煩わしいという場合は設定しない方がよさそうです。(RequiredFieldValidator など他の検証コントロールは ControlToValidate プロパティを設定しないとエラーになりますので注意してください。CustomValidator だけ特別です)。
  4. どういう html 要素がどのタイミングで change イベントを発生させるかについては MDN の記事 HTMLElement: change event を見てください。その記事に書いてある通り、TextBox はユーザーが入力してフォーカスを外した時、DropDownList はユーザーが選択を変更したとき、FileUpload はユーザーがファイルを選択したとき change イベントが発生し、CustomValidator の ControlToValidate プロパティが設定されていれば検証がかかります。

    CheckBox (input type="checkbox"), RadioButton (input type="radio") も change イベントは発生しますが、上に述べたように CustomValidator の ControlToValidate プロパティを設定できないので、change イベントでは CustomValidator による検証はかかりません(submit で検証されます)。
  5. クライアント側での検証用 JavaScript のメソッドは CustomValidator の ClientValidationFunction プロパティに設定します。メソッド名が例えば Validate(sender, args) とすると、sender には CustomValidator が html に変換された span 要素が渡されます。args には IsValid, Value プロパティを持つ JavaScript オブジェクトが渡されます。

    CustomValidator の ControlToValidate プロパティが検証対象コントロールに対して設定してある場合は、args.Value には検証対象の入力コントロールが html に変換された input 要素の value 属性の値が渡されます。ControlToValidate プロパティが設定されてない場合は args.Value は空 "" になります。

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" 
    AutoEventWireup="true" CodeBehind="WebForm6.aspx.cs" 
    Inherits="WebApplication1.WebForm6" %>

<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" 
    runat="server">

    <script type="text/javascript">
        //<![CDATA[

        // ControlToValidate が設定されてない場合、引数 args には
        // テキストボックスの値が渡されないので注意。args に頼らず
        // 以下のようにしておくのがよさそう
        function TextBoxValidate(sender, args) {
            var tb =
                document.getElementById('<%= TextBox1.ClientID%>');

            var membership = tb.value.toLowerCase();
            if (membership === "gold" || membership === "silver") {
                args.IsValid = true;
            } else {
                args.IsValid = false;
            }
        }

        function CheckBoxValidate(sender, args) {
            var cb =
                document.getElementById('<%= CheckBox1.ClientID%>');

            if (cb.checked == true) {
                args.IsValid = false;
            } else {
                args.IsValid = true;
            }
        }

        function RadioButtonValidate(sender, args) {
            var rb =
                document.getElementById('<%= RadioButton1.ClientID%>');

            if (rb.checked == true) {
                args.IsValid = false;
            } else {
                args.IsValid = true;
            }
        }

        // ControlToValidate が設定されてない場合、引数 args には
        // テキストボックスの値が渡されないので注意。args に頼らず
        // 以下のようにしておくのがよさそう
        function DropDwonListValidate(sender, args) {
            var ddl =
                document.getElementById('<%= DropDownList1.ClientID%>');

           if (ddl.value == "2") {
                args.IsValid = false;
            } else {
                args.IsValid = true;
            }
        }

        function FileUploadValidate(sender, args) {
            if (window.File && window.FileList) {
                var fileUpload =
                    document.getElementById("<%=FileUpload1.ClientID%>");

                if (fileUpload.files[0] == null) {
                    args.IsValid = false;
                    return;
                }

                if (fileUpload.files[0].type != "image/jpeg") {
                    args.IsValid = false;
                    return;
                }


            } else {
                args.IsValid = true;
            }
            
        }

        //]]>
    </script>

</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" 
    runat="server">

    <h1>CustomValidator</h1>
    <p>CustomValidator のクライアント側での検証のタイミング</p>

    <table>
        <tr>
            <td>
                TextBox
            </td>
            <td>
                <asp:TextBox ID="TextBox1" runat="server">
                </asp:TextBox>
            </td>
            <td>
                <asp:CustomValidator ID="CustomValidator1" 
                    runat="server" 
                    ForeColor="Red"
                    Display="Dynamic"
                    ErrorMessage="Gold または Silver でない" 
                    ClientValidationFunction="TextBoxValidate" 
                    ControlToValidate="TextBox1">
                </asp:CustomValidator>
            </td>
        </tr>
        <tr>
            <td>
                CheckBox
            </td>
            <td>
                <asp:CheckBox ID="CheckBox1" runat="server" />
            </td>
            <td>
                <%--ControlToValidate="CheckBox1" を設定すると
                    HttpException がスローされる。理由は CheckBox
                    には ValidationProperty 属性が付与されてない
                    から。なので submit しないと検証はかからない
                    --%>
                <asp:CustomValidator ID="CustomValidator2" 
                    runat="server"
                    ForeColor="Red"
                    Display="Dynamic"
                    ErrorMessage="チェック不可" 
                    ClientValidationFunction="CheckBoxValidate">
                </asp:CustomValidator>
            </td>
        </tr>
        <tr>
            <td>
                RadioButton
            </td>
            <td>
                <asp:RadioButton ID="RadioButton1" runat="server" />
            </td>
            <td>
                <%--ControlToValidate="RadioButton1" を設定すると
                    HttpException がスローされる。理由は RadioButton
                    には ValidationProperty 属性が付与されてないから。
                    なので submit しないと検証はかからない
                    --%>
                <asp:CustomValidator ID="CustomValidator3" 
                    runat="server"
                    ForeColor="Red"
                    Display="Dynamic"
                    ErrorMessage="選択不可" 
                    ClientValidationFunction="RadioButtonValidate">
                </asp:CustomValidator>
            </td>
        </tr>
        <tr>
            <td>
                DropDownList
            </td>
            <td>
                <asp:DropDownList ID="DropDownList1" runat="server">
                    <asp:ListItem>0</asp:ListItem>
                    <asp:ListItem>1</asp:ListItem>
                    <asp:ListItem>2</asp:ListItem>
                </asp:DropDownList>
            </td>
            <td>
                <asp:CustomValidator ID="CustomValidator4" 
                    runat="server"
                    ForeColor="Red"
                    Display="Dynamic"
                    ErrorMessage="2 は選択不可" 
                    ClientValidationFunction="DropDwonListValidate" 
                    ControlToValidate="DropDownList1">
                </asp:CustomValidator>
            </td>
        </tr>
        <tr>
            <td>
                FileUpload
            </td>
            <td>
                <asp:FileUpload ID="FileUpload1" runat="server" />
            </td>
            <td>
                <asp:CustomValidator ID="CustomValidator5" 
                    runat="server"
                    ForeColor="Red"
                    Display="Dynamic"
                    ErrorMessage="jpg ファイル以外不可" 
                    ClientValidationFunction="FileUploadValidate" 
                    ControlToValidate="FileUpload1">
                </asp:CustomValidator>
            </td>
        </tr>
    </table>
    <asp:Button ID="Button1" runat="server" Text="Submit" />

</asp:Content>

上のコードはマスターページを利用しています。先の記事「ASP.NET 4.5 ScriptManager」で書きましたように、ASP.NET 4.5 以降でクライアントスクリプトを利用するサーバーコントロールが正しく機能するには、必要なクライアントスクリプトの ScriptManager への登録と全ページでの ScriptManager の配置が必要です。マスターページを使ってそのあたりを解決しています。

Tags: , , ,

Validation

CustomValidator で jQuery.ajax 利用

by WebSurfer 2016年10月20日 23:18

ASP.NET Web Forms アプリで、ユーザー入力の検証に CustomValidator と jQuery.ajax を用い、クライアント側での検証を行うというコードを書いたときに、無知な自分がハマったことを忘れないように備忘録として書いておきます。

CustomValidator

シナリオは、ユーザー入力が有効かどうかサーバー側でなければ確認できない場合、クライアント側から jQuery.ajax を使ってサーバーに問合せて検証し、検証結果が NG であればエラーメッセージを表示してユーザーに再入力を促すというものです。

CustomValidator は検証用のコードを自力で書かなければなりませんが、その分自由度が高く、クライアント側での検証用スクリプトを自分で書いて、jQuery.ajax を使ってサーバー側に問い合わせ、その結果で検証するようにできます。

(ちなみに、RequiredFieldValidator とか RegularExpressionValidator などは、クライアント側での検証用スクリプトとサーバー側での検証用コードの両方とも ASP.NET が自動的に生成してくれます)

また、ASP.NET Web Forms 用に多々用意されている検証コントロールの一つなので、他の検証コントロールと合わせて、全体的に整合が取れたユーザー入力の検証を行うことができます。

というわけで、今回のシナリオには CustomValidator を使うのがよさそうなので、CustomValidator と jQuery.ajax を組み合わせて使ったサンプルコードを書いてみました。下にアップしたのがそれです。

Microsoft が提供するサンプルデータベース Northwind の Customers テーブルを利用し、ユーザーが TextBox に入力した文字列が Customers テーブルの CustomerID フィールドに存在しない場合は検証 NG としています。他に、未入力および入力形式(アルファベット 5 文字)の検証もしています。

で、自分がハマったことですが、以下の 1 と 2 です。3 以降は自分的に備忘録として残しておいた方がよさそうだと思って書きました。下のサンプルコードを参照しながら読んでください。

  1. jQuery.ajax の設定 の async オプションを false に設定する。
    デフォルトは true で「非同期」になります。非同期では、検証用スクリプトの ClientValidate 関数は、$.ajax({ ... }); が実行されると、success, fail オプションに設定されたコールバックが実行されないまま即完了してしまいます。結果、ClientValidate 関数では args.IsValid には何も設定されません(初期値の true のままになる)。
    なので async:false に設定する必要があります。API Documentation の説明によると "Note that synchronous requests may temporarily lock the browser, disabling any actions while the request is active." とのことなので、あまりお勧めはできないようですが、他に手段が見つからないのでやむを得ません。
    なお、async:false とした場合は、jqXHR.done() は使わないで、success, error, complete にコールバックを書けと言うことですのでそうしてあります。
  2. jQuery.ajax によるサーバーへの要求が 2 回出てしまうケースがある。
    TextBox にアルファベット 5 文字を入力してから(正規表現による検証はパスさせてから)Button をクリックすると 2 回要求が出てしまいます(1 回無駄)。理由は、Button クリックで TextBox からフォーカスが外れて検証がかかり、Button クリックでもう一度検証かかかるからです。
    もともとそのようなケースでは検証が 2 回かかってしまうのが仕様(?)のようですが、今回のようにサーバーのデータベースへの問合せが行われており、それが 1 回余分になるのは何とかした方がよさそうな気がします。回避方法は調査中です。(見つからないかも)
  3. ユーザーが未入力の場合の検証は RequiredFieldValidator を利用しています。
    先の記事「CustomValidator と RequiredFieldValidator」で書きましたが、CustomValidator で未入力の検証も可能です。しかし、検証コントロールのエラーメッセージは固定的なので、CustomValidator 一つで済ませる場合、エラーメッセージは "未入力もしくはデータベースに存在しません" というようにせざるを得ません。
    それより、RequiredFieldValidator と CustomValidator を併用して、未入力の時は "未入力です" というエラーメッセージを、入力はあるが無効な場合は "データベースに存在しません" とした方がユーザーフレンドリーだと思います。
  4. 上で「検証コントロールのエラーメッセージは固定的」と書きましたが、サーバー側での検証メソッドでは検証内容に応じてエラーメッセージを書き換えることができます。
    しかしながら、先の記事「FileUpload と CustomValidator」でもいろいろ考えましたが、クライアント側でのエラーメッセージを書き換える方法が分かりません。それも RequiredFieldValidator と CustomValidator を併用した理由です。
  5. RegularExpressionValidator と併用すると表示が 2 重になる。
    最初、RequiredFieldValidator で未入力の検証、RegularExpressionValidator でアルファベット 5 文字であることの検証、CustomValidator でデータベースにデータがあるかの検証を行うつもりでした。
    しかしユーザー入力があって RequiredFieldValidator での検証をパスした場合、RegularExpressionValidator と CustomValidator の両方の検証がかかり、両方のエラーメッセージ(例えば、「アルファベット 5 文字としてください」と「データベースに存在しません」)が出てしまいます。
    それが、以下のサンプルコードで、CustomValidator でアルファベット 5 文字とデータベースの有無の両方の検証をすることにした理由です。
  6. 上の画像のエラーメッセージ "CustomValidator" は、CustomeValidator をドラッグ&ドロップしたときのデフォルトです。
    ユーザー入力 abcde はデータベースに存在しないので、クライアント側での検証結果が NG となり、ErrorMessage プロパティに設定されたエラーメッセージ "CustomValidator" が赤文字で表示されたところです。
    もちろんこれは初期設定で変更できます。サーバー側ではエラーメッセージを動的に書き換えることもできます。下のコードで、クライアント側での検証が働かないようにして(ClientValidate 関数の中のコードを全てコメントアウトするなどして)サーバー側だけで検証をかけると、ServerValidate メソッドで書き換えられたエラーメッセージが表示されます。
    ただし、上にも書きましたが、クライアント側でのエラーメッセージは固定的になります。
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Text.RegularExpressions " %>
<%@ Import Namespace="System.Web.Configuration" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Web.Services" %>

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

<script runat="server">

  // jQuery.ajax で POST された文字列を受け、DB に存在するか
  // をチェックするためのページ内の静的メソッド。
  // public static にして WebMethodAttribute 属性を付与
  [WebMethod]
  public static string ValidateId(string id)
  {
    // Northwind サンプルデータベース Customers テーブルの
    // CustomerID フィールドにユーザーが入力した文字列と同じ
    // データが存在するかを調べ、存在すればその CustomerID
    // データを、無ければ null を返す        
    string query = "SELECT CustomerID FROM Customers " + 
                   "WHERE CustomerID = @CustomerID";
    string connString = WebConfigurationManager.
        ConnectionStrings["NORTHWINDConnectionString"].
        ConnectionString;

    using (SqlConnection conn = new SqlConnection(connString))
    {
      using (SqlCommand cmd = new SqlCommand(query, conn))
      {
        cmd.Parameters.Add("@CustomerID", SqlDbType.NChar);
        cmd.Parameters["@CustomerID"].Value = id;
        conn.Open();
        return (string)cmd.ExecuteScalar();
      }
    }
  }
    
  // CustomValidator のサーバー側での検証用メソッド
  protected void ServerValidate(object source, 
      ServerValidateEventArgs args)
  {
    // CustomerID はアルファベット 5 文字なので、まず
    // 正規表現でアルファベット 5 文字にマッチするかを
    // チェック
    string pattern = "^[a-zA-Z]{5}$";

    if (Regex.IsMatch(args.Value, pattern))
    {
      // 上のヘルパメソッドを流用。DB にデータが
      // 存在しない場合 null が id に代入される
      string id = ValidateId(args.Value);

      if (id != null)
      {
        args.IsValid = true;
      }
      else
      {
        // サーバー側でならエラーメッセージを書き換
        // えることが可能
        ((CustomValidator)source).ErrorMessage = 
                    "データベースに存在しません";
        args.IsValid = false;
      }
    }
    else
    {
      ((CustomValidator)source).ErrorMessage = 
              "アルファベット 5 文字としてください";
      args.IsValid = false;
    }
  }   

  protected void Button1_Click(object sender, EventArgs e)
  {
    // IsValid で検証結果を調べて処置を行うのが原則
    if (Page.IsValid)
    {
      Label1.Text = "OK";
    }
    else
    {
      Label1.Text = "NG";
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <script src="/Scripts/jquery-1.11.1.js" type="text/javascript">
  </script>
  <script type="text/javascript">
  //<![CDATA[
    // async: false を設定しないとコールバックは非同期で実行
    // され args.IsValid が設定できないので注意
    // TextBox に入力してから Button をクリックすると 2 回要
    // 求が出でしまう。理由は、Button クリックする際 TextBox
    // からフォーカスが外れて検証がかかり、Button クリックで
    // もう一度検証かかかるから(デフォルトでそういう仕様)
    function ClientValidate(sender, args) {
      // 正規表現パターン(アルファベット 5 文字)            
      if (args.Value.match(/^[a-zA-Z]{5}$/)) {
        $.ajax({
          type: "POST",
          async: false,
          url: "0180-CustomValidatorAjax.aspx/ValidateId",
          data: '{"id":"' + args.Value + '"}',
          contentType: "application/json; charset=utf-8",

          success: function (data) {
            // .NET 3.5 で追加された d パラメータの処置
            if (data.hasOwnProperty('d')) {
              data = data.d;
            }
            if (data) {
              args.IsValid = true;
            } else {
              args.IsValid = false;
            }
          },

          error: function (jqXHR, textStatus, errorThrown) {
            args.IsValid = false;
          }
        });
        } else {
          args.IsValid = false;
        }
      }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
    
  <asp:TextBox ID="TextBox1" runat="server">
  </asp:TextBox>
    
  <asp:RequiredFieldValidator 
    ID="RequiredFieldValidator1" 
    runat="server" 
    ErrorMessage="必須入力です" 
    Display="Dynamic"
    ControlToValidate="TextBox1" 
    ForeColor="Red">
  </asp:RequiredFieldValidator>

  <asp:CustomValidator 
    ID="CustomValidator1" 
    runat="server" 
    ErrorMessage="CustomValidator" 
    ControlToValidate="TextBox1" 
    Display="Dynamic"
    ForeColor="Red" 
    OnServerValidate="ServerValidate" 
    ClientValidationFunction="ClientValidate">
  </asp:CustomValidator>

  <br />
  <asp:Button ID="Button1" runat="server" 
    Text="送信" OnClick="Button1_Click" />
  <br />
  <asp:Label ID="Label1" runat="server"></asp:Label>
  </form>
</body>
</html>

Tags: ,

Validation

FileUpload と CustomValidator

by WebSurfer 2015年3月10日 14:02

ASP.NET サーバーコントロールの FileUpload を使用してファイルをアップロードする際に、検証コントロールの CustomValidator を利用してファイルのサイズとタイプを検証し、検証結果 NG の場合はファイルをセーブしない方法について書きます。

アップロードされるファイルの検証

注:この記事は単一ファイルのアップロードの場合に限った話です。(ASP.NET 4.5 以降では FileUpload コントロールに AllowMultiple, HasFiles, PostedFiles プロパティなどが追加されています。なので、Web サーバーが ASP.NET 4.5 以降、ブラウザが HTML5 をサポートしていれば FileUpload を使っての複数ファイルの同時アップロードが可能です・・・が、それは今回は考えていません)

ファイルのアップロード後に、サーバー側だけでファイルのサイズやタイプを検証するのであれば、CustomValidator を使うメリットはあまりなさそうです。

しかし、RequiredFieldValidator, RegularExpressionValidator 等と同様に、ユーザー入力を submit する前にクライアント側で検証し、検証結果 NG の場合は submit を中止してエラーメッセージを表示するという機能を実装する場合は CustomValidator を利用するのがお勧めです。

理由は、(1) 他の ASP.NET 検証コントロールとの統一性が保てること、(2) 検証結果 NG の場合の submit の中止とエラーメッセージの表示のため検証コントロールの既存の機能が使えるということです。(特に (2) は ASP.NET の仕組みをよく理解していないと自力で実装するのは難しいと思われます)

CustomValidator を利用する場合、サーバー側での検証に使うメソッド、クライアント側での検証に使う JavaScript 関数のコードは自力で書かなければなりません。

サーバー側は特に難しくはないと思いますが、問題はクライアント側です。もともと JavaScript にはローカルファイルにアクセスする機能はありません。なので、JavaScript ではファイルのサイズやタイプなどの情報は取得できず、クライアント側での検証は諦めざるを得ませんでした。

ところが HTML5 によって話が変わってきました。今は、ブラウザが HTML5 File API 仕様をサポートしていれば、その API を利用して JavaScript でファイルのサイズやタイプなどの情報を取得でき、クライアント側での検証が可能が可能です。

以下に CustomValidator を使用して、クライアント側とサーバー側の両方でファイルのサイズとタイプを検証し、サイズが 500,000 bytes を超える場合、タイプが image/jpeg 以外の場合は検証 NG としてファイルのアップロードを中断するサンプルコードをアップしておきます。

<%@ 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">
  // サイズ制限 500,000 bytes, タイプ制限 image/jpeg とした。
  // ハードコーディングしないで web.config で appSettings に
  // 設定する方が良いかも。
  int maxFileSize = 500000;
  string allowedContentType = "image/jpeg";
    
  protected void Page_Load(object sender, EventArgs e)
  {
    Label1.Text = "";
  }
    
  protected void Button1_Click(object sender, EventArgs e)
  {
    if (Page.IsValid)
    {
      // FileUploadTest はアプリケーションルート直下に作った
      // ファイル格納用フォルダ。その物理パスを取得
      string savePath = MapPath("~/FileUploadTest/");

      savePath += Server.HtmlEncode(FileUpload1.FileName);

      FileUpload1.SaveAs(savePath);

      // アップロード完了時のメッセージ表示
      Label1.Text = FileUpload1.FileName +
        " (" + FileUpload1.PostedFile.ContentType + ") - " +
        FileUpload1.PostedFile.ContentLength.ToString() + 
        " bytes アップロード完了";
    }
  }

  // サーバー側の検証に使うメソッド。
  // ServerValidate イベントのハンドラに設定する 
  protected void ServerValidate(object source, 
      ServerValidateEventArgs args)
  {
    // FileUpload にファイルが格納されていることを確認
    if (FileUpload1.HasFile)
    {
      // MIME コンテンツタイプの取得 / 制限
      string contentType = FileUpload1.PostedFile.ContentType;
      if (contentType != allowedContentType)
      {
        ((CustomValidator)source).ErrorMessage =
              "タイプが " + allowedContentType + 
              "ではありません。";
        args.IsValid = false;
        return;
      }
        
      // ファイルサイズの取得 / 制限
      int fileSize = FileUpload1.PostedFile.ContentLength;
      if (fileSize > maxFileSize)
      {
        ((CustomValidator)source).ErrorMessage =
            "サイズが " + maxFileSize.ToString() +
            " bytes を超えています。";
        args.IsValid = false;
        return;
      }

      args.IsValid = true;
    }
    else
    {
      ((CustomValidator)source).ErrorMessage = 
          "ファイルが選択されていません";
      args.IsValid = false;
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>FileUpload</title>
  <script type="text/javascript">
  //<![CDATA[
    // クライアント側での検証用 JavaScript 関数。
    // ClientValidationFunction プロパティに設定する
    function ClientValidate(val, args)
    {
      // HTML5 File API の File, FileList がサポートされている
      // 場合のみ検証する。サポートされてないとスクリプトエラ
      // ーになるので下の if 文は必要
      if (window.File && window.FileList) 
      {
        var fileUpload = 
          document.getElementById("<%=FileUpload1.ClientID%>");

        // if (fileUpload.files[0] == null) でも引っかかるがファイルが
        // 選択されてないと fileUpload.files[0] は undefined になるので
        // 以下のようにするのが正解かも
        if (fileUpload.files[0] == undefined ||
            fileUpload.files[0] == null) {
        {
          // ここでサーバー側と同様に ErrorMessage プロパティ
          // に設定されたエラーメッセージを書き換えようとした
          // が不可。ウラワザ的方法があるかもしれないが諦めた。
          // 下の alert はデバッグ時の検証用
          alert("ファイル未選択");
          args.IsValid = false;
          return;
        }

        if (fileUpload.files[0].type != '<%=allowedContentType%>') 
        {
          // alert はデバッグ時の検証用
          alert("タイプ相違");
          args.IsValid = false;
          return;
        }

        // 2021/1/26 訂正
        // Number() を使わないと Chrome, Edge で Synrax error になる。
        // ブログの記事を書いた 2015/3/10 時点では問題なかったが
        // JavaScript の仕様が変わった?
        if (fileUpload.files[0].size > Number(<%=maxFileSize%>)) {
        {
          // alert はデバッグ時の検証用
          alert("サイズオーバー");
          args.IsValid = false;
          return;
        }
      }
      args.IsValid = true;
    }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h4>Select a file to upload:</h4>

    <asp:FileUpload id="FileUpload1"                 
      runat="server">
    </asp:FileUpload>
           
   <%-- CustomValidator だけは、ControlToValidate プロ
    パティを設定しなくても使用可。ValidateEmptyText プ
    ロパティはデフォルトの false のままで検証できる。
    ControlToValidate="FileUpload1" としておくと、
    ファイルを選択した時点でクライアント側の検証がかかる。
    ただし、そうすると未選択時にはクライアント側での検証
    はかからなくなるので注意--%>
    <asp:CustomValidator 
      ID="CustomValidator1" 
      runat="server" 
      ForeColor="Red"
      OnServerValidate="ServerValidate" 
      ClientValidationFunction="ClientValidate"            
      ErrorMessage="CustomValidator">
    </asp:CustomValidator>
    <br/>
    <br/>

    <asp:Button id="Button1" Text="Upload file"
      OnClick="Button1_Click" runat="server">
    </asp:Button>

    <hr />

    <asp:Label id="Label1" runat="server" />
  </div>
  </form>
</body>
</html>

自分が持っているブラウザで試した限りですが、File API がサポートされていてクライアント側での検証が可能だったのは、Firefox 36.0.1, Chrome 41.0.2272.76, Safari 5.1.7, Opera 12.17 でした。

IE10 以上も File API をサポートしているそうですが、自分は持ってないので検証できていません。IE9 で検証してみましたが、やはり File API は未サポートのようで、クライアント側での検証はかかりません。ただし、サーバー側での検証はかかるので、そこでは制限できます。

この記事の上にある画像は、上のサンプルコードを Chrome で表示した時のもので、MIME タイプが image/jpeg ではないのでクライ��ント側での検証結果が NG となり、ErrorMessage プロパティに設定されたエラーメッセージ "CustomValidator" が赤文字で表示されたところです。

注: CustomValidator の ControlToValidate プロパティを "FileUpload1" に設定しておくと、ファイルを選択した時点でクライアント側の検証がかかります。ただし、そうすると未選択時にはクライアント側での検証はかからなくなるので注意してください。上のサンプルコードでは ControlToValidate を設定 していませんので、submit(すなわち[Upload file]ボタンクリック)時点でクライアント側の検証がかかっています。

エラーメッセージ "CustomValidator" は、CustomeValidator をドラッグ&ドロップしたときのデフォルト ErrorMessage="CustomValidator" で、もちろんこれは初期設定で変更できます。ただし、クライアント側でエラーの内容に応じてこれを動的に書き換えることはできないようです(サーバー側では上のコードのようにして書換可能でしたが)。

最後に、HTML5 File API に関して参考になると思われる(と言うか自分が参考にした)記事へのリンクを張っておきます。

Tags: , ,

Validation

About this blog

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

Calendar

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

View posts in large calendar