WebSurfer's Home

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

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

IE の互換表示設定と meta タグ

by WebSurfer 2015年2月24日 14:19

先の記事 ブラウザーモードとドキュメントモード で、meta タグ (X-UA-Compatible) に content="IE=edge" を指定すると IE6 ~ IE11 では "the highest standards mode supported by Internet Explorer" になるという話を書きました。

これと IE の互換表示設定(以下の画像参照)のどちらが優先されるでしょうか?

IE の互換表示設定

実際に検証した結果、meta タグの指定の方でした。(検証に使ったのは IE9 です。【2021/7/31 追記】 IE11 バージョン 11.789.19041.0 でも検証しました。他のバージョンは未検証)

上の画像のようにデフォルトでは[イントラネットのサイトを互換表示で表示する(I)]にチェックが入っていますので、あるサイトをローカルイントラネットに登録すると(登録しなくても、ホスト名によっては IE が勝手にイントラネットと判定するケースもありますが)、そのサイトは互換モードで表示されます。

または、上の画像で[追加(A)]ボタンをクリックして[互換表示に追加した Web サイト(W):]に加えても、そのサイトは互換モードで表示されます。

上記の設定のまま、あるページだけは標準モードで表示したい場合、そのページに以下の meta タグを追加すれば "the highest standards mode supported by Internet Explorer" 即ち標準モードで表示されるようになります。

<meta http-equiv="x-ua-compatible" content="IE=edge" />

標準モードでは表示が崩れるなどの理由で互換モードで表示したい場合のみに meta タグを使用してドキュメントモードを指定するのだと思い込んでいましたが、その逆(互換モード ⇒ 標準モード)も可能だったようです。

実は、つい先日 MSDN Forum で教えてもらうまで知らなかったです。(汗)

Tags:

その他

クライアント側検証の無効化

by WebSurfer 2015年2月17日 18:40

先の記事 コレクションのデータアノテーション検証 でユーザー入力の検証の話を書きましたが、デフォルトで有効になっているクライアント側での検証を無効にするにはどうすればいいかという話を書きます。

ユーザー入力の検証

これは、Visual Studio 2010 で MVC4 アプリを インターネットアプリケーション テンプレートで作った場合の話で、Visual Studio 2013 とか MVC5 とかでは違うかもしれませんのでご注意ください。

アプリケーションルート直下の web.config での以下の定義がされており、クライアント側での検証がアプリケーション全体で有効に設定されています。

<appSettings>
  ・・・中略・・・
  <add key="ClientValidationEnabled" value="true" />
  ・・・中略・・・
</appSettings>

クライアント側での検証を無効にしたいことがあるとすると多分特定の View だけでしょうから、その場合は当該 View のコードに下記を追加すれば OK です。

@{
  Html.EnableClientValidation(false);
}

クライアント側検証には EnableClientValidation の他に EnableUnobtrusiveJavaScript が使われていますが、stackoverflow の記事 によると、後者は Ajax にも使われていて、false にすると Ajax が動かなくなるとのことなので注意してください。

以上で話は終わりなのですが、それだけでは記事としてはちょっと面白くないので、上記に関連して調べたことを備忘録として書いておきます。(笑)

MVC4 の新機能として JavaScript / CSS ファイルの縮小化と結合処置の自動化機能があり、その機能を利用するため、App_Start フォルダの BundleConfig.cs ファイルに以下の定義がされています。

public class BundleConfig
{
  public static void RegisterBundles(BundleCollection bundles)
  {
    bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js"));

    bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                        "~/Scripts/jquery-ui-{version}.js"));

    bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/jquery.unobtrusive*",
                        "~/Scripts/jquery.validate*"));

    // ・・・中略・・・
  }
}

アプリケーションルート直下の Scripts フォルダには jquery-1.7.1.js, jquery.validate.js その他必要な jQuery 関係の外部スクリプトファイルが自動的に配置されます。

Global.asax には以下のコードが記述されており、アプリケーション起動時に外部スクリプトファイルのパスが ASP.NET の BundleCollection オブジェクトに登録されるようになっています。(詳しくは @IT の記事 Visual Studio 2012の新機能とASP.NET 4.5のコア機能 (3/4) を見てください)

public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
      // ・・・中略・・・

      BundleConfig.RegisterBundles(BundleTable.Bundles);

      // ・・・中略・・・
  }
}    

BundleCollection オブジェクトに登録した外部スクリプトファイルを参照するには、View で Scripts.Render メソッド を使用します。

具体的には _Layout.cshtml(Web Forms アプリのマスターページに相当)の @Scripts.Render("~/bundles/jquery") と View の @Scripts.Render("~/bundles/jqueryval") で以下の外部スクリプトファイルへの参照が HTML ソースに定義されます。

<script src="/Scripts/jquery-1.7.1.js"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>

(注)上記は <compilation debug="true" ... /> の場合です。debug="false" の場合はファイルの縮小化と結合処置の自動化機能が働いて以下のようになります。

<script src="/bundles/jquery?v=1A_Q..."></script>
<script src="/bundles/jqueryval?v=-tc2Q..."></script>

以上ような設定において、例えば Model にアノテーション属性を以下のように付与したとします。

public class Parent
{
  //・・・中略・・・

  [Required(ErrorMessage = "{0} は必須")]
  [StringLength(5, ErrorMessage = "{0} は {1} 文字以内")]
  [Display(Name = "Parent Name")]
  public string Name { get; set; }

  //・・・中略・・・
}

そして、View のコードを以下のように記述したとします。

<div class="editor-label">
  @Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
  @Html.EditorFor(model => model.Name)
  @Html.ValidationMessageFor(model => model.Name)
</div>

そうすると、クライアント側検証が有効の場合(Html.EnableClientValidation(true) の時)は、ASP.NET がレンダリングする HTML ソースは以下のようになります。

<div class="editor-label">
  <label for="Name">Parent Name</label>
</div>
<div class="editor-field">
  <input 
    class="text-box single-line" 
    data-val="true" 
    data-val-length="Parent Name は 5 文字以内" 
    data-val-length-max="5" 
    data-val-required="Parent Name は必須" 
    id="Name" 
    name="Name" 
    type="text" 
    value="" />
  <span 
    class="field-validation-valid" 
    data-valmsg-for="Name" 
    data-valmsg-replace="true">
  </span>
</div>

上記のように input 要素に付与された data-val* 属性に従って、参照された外部スクリプトに含まれる Unobtrusive(控えめな)JavaScript がクライアント側での検証を行うようになります。

一方、クライアント側検証を無効にした場合(Html.EnableClientValidation(false) の時)は ASP.NET がレンダリングする HTML ソースは以下のようになります。(注:外部スクリプトファイルへの参照は変わりません)

<div class="editor-label">
  <label for="Name">Parent Name</label>
</div>
<div class="editor-field">
  <input 
    class="text-box single-line" 
    id="Name" 
    name="Name" 
    type="text" 
    value="" />
</div>

クライアント側での検証はかかりませんがサーバー側で検証は行われ、検証結果が NG の場合は input 要素の直後に以下の span 要素が追加され、エラーメッセージ(上の画像の赤い文字)が表示されます。

<span class="field-validation-error">
  Parent Name は必須
</span>

Tags: ,

Validation

About this blog

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

Calendar

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

View posts in large calendar