WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

ファイルアップロード時の検証 (CORE 版)

by WebSurfer 26. January 2021 22:32

ASP.NET Core. 3.1 MVC アプリでファイルをアップロードする際に、カスタム検証属性を利用してファイルのサイズとタイプをクライアント側とサーバー側の両方で検証し、検証結果 NG の場合はエラーメッセージを表示する方法について書きます。

ファイルアップロード時の検証

基本的には先の記事「ASP.NET Core MVC 検証属性の自作」の応用です。先の記事ではパラメータが 1 つだけでしたが、この記事ではファイルのサイズとタイプという 2 つを検証するところが大きな違いです。

クライアント側での検証は、Web Forms アプリの記事「FileUpload と CustomValidator」と同様に、ブラウザの HTML5 File API を利用して JavaScript でファイルのサイズとタイプ情報を取得し、外部から入力されるパラメータ値と比較して検証します。

そのパラメータ値はカスタム検証属性(下のコード例では FileValidationAttribute)をモデルのプロパティに設定する際に引数(下のコード例では 50000 と "image/jpeg")として渡すようにしています。

サーバー側では引数に渡されたパラメータ値(ファイルサイズ / タイプ)を容易に取得でき、検証属性クラスの IsValid メソッドに引数として渡される IFromFile オブジェクトから取得できるファイルサイズとタイプを比較して検証できます。問題はクライアント側での検証用 JavaScript / jQuery にそのパラメータ値をどのように渡すかです。

パラメータが 1 つだけなら先の記事「ASP.NET Core MVC 検証属性の自作」で書いた View の中のスクリプトのように addSingleVal メソッドを利用できます。しかし、2 つあると addSingleVal メソッドでは何ともならず、add メソッドを利用せざるを得ないようです。

add メソッドの使い方は、先の記事を書くときに調べた Unobtrusive Client Validation in ASP.NET MVC 3 という記事に記述がありましたが、具体的にどういうコードを書けばいいのかが分かりません。

やむを得ず検証用の控えめな JavaScript のソースコード jquery.validate.unobtrusive.js を調べると、addBool, addSingleVal, addMinMax メソッドは add メソッドをラップしたものだということが分かりました。その中の addMinMax メソッドが min, max というパラメータを 2 つ渡していますので、それを見よう見まねで書いてみたのが下の View の中のコードです。

Edge, Chrome のデバッガでスクリプトをステップ実行するなどして調べましたが一応は期待通り動いていることを確認しました。具体的にどのような仕組みで動いているかまでは分かっていませんが。(汗)

いかにサンプルコードをアップしておきます。上の画像を表示したものです。要点はコメントに書いたつもりですのでそれを見てください。(手抜きでスミマセン)

Model とカスタム検証属性

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Globalization;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MvcCoreApp.Models
{
    // Model
    public class FileUpload
    {
        [Required(ErrorMessage = "{0} is required.")]
        public string Name { get; set; }

        [Display(Name = "Upload file")]
        [Required(ErrorMessage = "{0} is required.")]
        [FileValidation(50000, "image/jpeg")]
        public IFormFile PostedFile { get; set; }
    }

    // カスタム検証属性
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class FileValidationAttribute : ValidationAttribute, 
                                           IClientModelValidator
    {
        private readonly long size;
        private readonly string type;

        public FileValidationAttribute(long size, string type)
        {
            this.size = size;
            this.type = type;
            this.ErrorMessage = "{0} must be less than {1} byte, {2}";
        }

        public override string FormatErrorMessage(string displayName)
        {
            return String.Format(CultureInfo.CurrentCulture, 
                                 ErrorMessageString, displayName, 
                                 this.size, this.type);
        }

        public override bool IsValid(object value)
        {
            var postedFile = value as IFormFile;

            if (postedFile == null || postedFile.Length == 0)
            {
                return false;
            }

            if (postedFile.Length <= this.size && 
                postedFile.ContentType == this.type)
            {
                return true;
            }

            return false;
        }
        // IClientModelValidator が実装するメソッド。検証対象の input
        // 要素に控えめな JavaScript (jquery.validate.unobtrusive.js)
        // による検証のための属性と値を追加
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            var errorMessage = FormatErrorMessage(
                context.ModelMetadata.GetDisplayName());
            MergeAttribute(context.Attributes, 
                "data-val-filevalidation", errorMessage);
            MergeAttribute(context.Attributes, 
                "data-val-filevalidation-size", this.size.ToString());
            MergeAttribute(context.Attributes, 
                "data-val-filevalidation-type", this.type);
        }

        // 上の AddValidation メソッドで使うヘルパーメソッド
        private bool MergeAttribute(IDictionary<string, string> attributes, 
                                    string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
            attributes.Add(key, value);
            return true;
        }
    }
}

View

@model MvcCoreApp.Models.FileUpload

@{
    ViewData["Title"] = "Upload";
}

<h1>Upload</h1>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Upload" enctype="multipart/form-data">
            <div asp-validation-summary="ModelOnly" class="text-danger">
            </div>
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>

            <div class="form-group">
                <label asp-for="PostedFile" class="control-label"></label>
                <input type="file" asp-for="PostedFile" />
                <br />
                <span asp-validation-for="PostedFile" class="text-danger">
                </span>
            </div>

            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}


<script type="text/javascript">
    //<![CDATA[
    $.validator.addMethod("filevalidation",
        function (value, element, parameters){
            // HTML5 File API の File, FileList がサポートされている
            // 場合のみ検証する。サポートされてないとスクリプトエラ
            // ーになるので下の if 文は必要
            if (window.File && window.FileList) {

                // ファイルが選択されてないと fileUpload.files[0] は
                // undefined になる。element.files[0] == null は念のため
                if (element.files[0] == undefined ||
                    element.files[0] == null) {
                    // ファイル未選択は RequiredAttribute で検証する
                    // こととし、ここでは true を返すことにした(検証
                    // とエラーメッセージがダブらないように)
                    return true;
                }

                if (element.files[0].type != parameters.type) {                    
                    return false;
                }

                if (element.files[0].size > Number(parameters.size)) {
                    return false;
                }

                return ture;
            } else {
                // HTML5 File API の File, FileList がサポートされて
                // いない場合はクライアント側では検証しない
                return ture;
            }
        });

    // パラメータを 2 つ渡す方法が問題。add メソッドを使わざるを得ない
    $.validator.unobtrusive.adapters.
        add("filevalidation", ["type", "size"], function (options) {

            // 上の検証スクリプトで parameters.type, parameters.size と
            // という形でパラメータを取得するには以下のように JavaScript
            // オブジェクトとして setValidationValues の第 3 引数に渡す。
            // 配列 [options.params.size,options.params.type] としても渡
            // せるが、取得するとき parameters[0] のようにする必要がある
            var value = {
                size: options.params.size,
                type: options.params.type
            };

            setValidationValues(options, "filevalidation", value);
        });

    // jquery.validate.unobtrusive.js からコピーしたヘルパーメソッド
    function setValidationValues(options, ruleName, value) {
        options.rules[ruleName] = value;
        if (options.message) {
            options.messages[ruleName] = options.message;
        }
    }
    //]]>
</script>
}

Comtroller / Action Method

using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using MvcCoreApp.Models;
using Microsoft.AspNetCore.Http;
using System.IO;
using Microsoft.AspNetCore.Mvc.Rendering;

namespace MvcCoreApp.Controllers
{
    public class ValidationController : Controller
    {
        public IActionResult Upload()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Upload(FileUpload model)
        {
            string result = "";
            IFormFile postedFile = model.PostedFile;

            if (ModelState.IsValid)            
            {
                string filename = Path.GetFileName(postedFile.FileName);
                
                result = filename + " (" + postedFile.ContentType + ") - " +
                        postedFile.Length + " bytes アップロード完了";
            }
            else
            {
                result = "ファイルアップロードに失敗しました";
            }

            ViewBag.Result = result;
            return View();
        }
    }
}

Tags: , , , ,

Upload Download

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

by WebSurfer 27. June 2020 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

FileUpload と CustomValidator

by WebSurfer 10. March 2015 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: , ,

Upload Download

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  February 2021  >>
MoTuWeThFrSaSu
25262728293031
1234567
891011121314
15161718192021
22232425262728
1234567

View posts in large calendar