WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

異なるフォルダのファイルをアップロード

by WebSurfer 9. July 2020 21:33

ブラウザを使って自分の PC の複数のフォルダから複数のファイルを一度にアップロードするにはどうしたらよいかという話を書きます。

複数ファイルをアップロード

html の input type="file" 要素を使うことが前提です。ブラウザがサポートしていれば input type="file" 要素に multiple="multiple" を追加すれば、ユーザーは複数のファイルを選択して一度にアップロードすることができます。

ファイルを選択する際、上の画像の[ファイル選択]ボタン(画像は Chrome の例です。ブラウザによって異なります)をクリックすると下の画像のダイアログが表示されますので、Shift キーや Ctrl キーを使って複数のファイルを選択できます。

送信するファイルの選択

その後[開く(O)]ボタンをクリックすると送信準備完了となり、form を method="post" で submit してやればサーバーに選択された複数のファイルが送信されます。

ただし、上の操作で送信するファイルを選択できるのは一回だけです。この後、もう一度同じ操作を行って別のファイルを追加することはできません。それをすると、前の操作で選択したファイルは送信対象には含まれなくなり、後の操作で選択したファイルのみが送信されるようになります。それは同じフォルダで繰り返し行う場合も異なるフォルダに移動して行う場合も同じです。

ということは、フォルダが異なるファイルを選択するには各フォルダに移動して選択操作を行わざるを得ませんが、送信できるのは最後の操作で選択したファイルのみになります。なので、複数のフォルダにある複数のファイルを一度にアップロードするのは普通のやり方(form を submit する方法)ではできないようです。

代案は HTML5 File APIFormData を利用し Ajax で送信することです。

以下に .NET Framework 版の ASP.NET MVC5 アプリの例を書いておきます。これは一番上の画像を表示したものです。詳しい説明はコードの中のコメントに書きましたのでそれを見てください。手抜きでスミマセン。

Model

MultipleUploadModels クラスをアクションメソッドの引数に設定すれば、送信されてきた複数ファイルは PostedFiles プロパティにモデルバインドされます。CustomField プロパティはファイル以外の追加情報を一緒に送信するためのものです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Mvc5App.Models
{
    public class MultipleUploadModels
    {
        public string CustomField { get; set; }
        public IList<HttpPostedFileBase> PostedFiles { get; set; }
    }
}

View

input type="file" 要素と input type="button" 要素(ボタン)を配置します。form 要素を生成するための html ヘルパー Html.BeginForm の引数(特に enctype = "multipart/form-data" とすること)に注意してください。

@section Scripts { ... } 内の JavaScript / jQuery のコードが HTML5 File API と FormData を利用し Ajax で複数フォルダ内の複数ファイルを送信するものです。このスクリプトがこの記事のキモです

@model Mvc5App.Models.MultipleUploadModels

@{
    ViewBag.Title = "MultipleUpload2";
}

<h2>MultipleUpload2</h2>

@using (Html.BeginForm("MultipleUpload2", "File", 
    FormMethod.Post, new { enctype = "multipart/form-data" }))
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>MultipleUploadModels</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })

        <div class="form-group">
            @Html.LabelFor(model => model.PostedFiles, 
                htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <input id="mutiplefileupload" type="file" 
                       name="postedfiles" multiple="multiple" />
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="button" id="ajaxUpload" value="Ajax Upload"
                       class="btn btn-default" />
                <br />
                <div id="result"></div>
            </div>
        </div>
        
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

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

        $(function () {
            // 複数回送信する場合、送信し終わったら fd をクリアして最初から
            // 始めないと、次に送信するとき前回選んだファイルも送信されダブ
            // ってしまう。このコードはそこは未対応なので注意。

            // ブラウザの HTML5 File API サポートを確認
            if (window.File && window.FileReader && window.FileList) {

                // CSRF 用のトークンを含んだ FormData オブジェクトを取得
                var fd = new FormData(document.querySelector("form"));

                // input type="file" 要素のオブジェクトを取得
                var fileUpload = document.getElementById("mutiplefileupload");

                // input type="file" 要素でダイアログを開いてファイルを選択
                // し[開く]ボタンをクリックすると、その都度 change イベン
                // トが発生する。それにリスナ(下のコードの function )を
                // アタッチして FormData オブジェクトを操作する
                fileUpload.addEventListener('change', function (e) {
                    // files プロパティで FileList オブジェクトを取得
                    var filelist = fileUpload.files;

                    // 一回の操作で複数のファイルを選択できるので、以下
                    // のようにループを回して
                    for (let i = 0; i < filelist.length; i++) {
                        // File オブジェクトを FormData に追加していく
                        fd.append("postedfiles", filelist[i]);
                    }
                });

                // [Ajax Upload] ボタンクリックの処置
                $('#ajaxUpload').on('click', function (e) {

                    // 追加データを以下のようにして送信できる。フォーム
                    // データの一番最後に追加されて送信される
                    fd.append("CustomField", "This is some extra data");

                    $.ajax({
                        url: '/file/multipleupload2',
                        method: 'post',
                        data: fd,
                        processData: false, // jQuery にデータを処理させない
                        contentType: false  // contentType を設定させない
                    }).done(function (response) {
                        $("#result").empty;
                        $("#result").html(response);
                    }).fail(function (jqXHR, textStatus, errorThrown) {
                        $("#result").empty;
                        $("#result").text('textStatus: ' + textStatus +
                            ', errorThrown: ' + errorThrown);
                    });
                });
            } else {
                $("#result").empty;
                $("#result").text('File API がサポートされてません。');
            }
        });
        //]]>
    </script>
}

Controller / Action Method

Ajax 呼び出しのみ可能としていますが、普通に input type="submit" ボタンをクリックして post した場合でもファイルのアップロードはできます。ただしその場合は最後のファイル選択操作で選んだファイルのみが送信されます。

using System.Web;
using System.Web.Mvc;
using Mvc5App.Models;
using System.IO;
using System.Collections.Generic;

namespace Mvc5App.Controllers
{
    public class FileController : Controller
    {
        public ActionResult MultipleUpload2()
        {
            return View();
        }

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult MultipleUpload2(MultipleUploadModels model)
        {
            if (!Request.IsAjaxRequest())
            {
                return Content("Ajax 呼び出しのみ可能");
            }

            string result = "";
            string customFiled = model.CustomField;
            IList<HttpPostedFileBase> postedFiles = model.PostedFiles;

            // ファイルが選択されてない場合でも postedFiles は null にならないし、
            // postedFiles.Count は 0 にならない(1 になる)。従い、以下のコード
            // では制御が else に飛ぶことはないので注意
            if (postedFiles != null && postedFiles.Count > 0)
            {
                foreach (HttpPostedFileBase postedFile in postedFiles)
                {
                    if (postedFile != null && postedFile.ContentLength > 0)
                    {
                        // アップロードされたファイル名を取得。ブラウザが IE の
                        // 場合 postedFile.FileName はクライアント側でのフル
                        // パスになることがあるので Path.GetFileName を使う
                        string filename = Path.GetFileName(postedFile.FileName);

                        // 保存ホルダの物理パス\ファイル名
                        string path = Server.MapPath("~/UploadedFiles") + 
                                      "\\" + filename;

                        // アップロードされたファイルを保存
                        postedFile.SaveAs(path);

                        result += filename + " (" + postedFile.ContentType + 
                                  ") - " + postedFile.ContentLength.ToString() +
                                  " bytes アップロード完了<br />";
                    }
                }
                result += "CustomField の文字列: " + customFiled;
            }
            else
            {
                result = "ファイルアップロードに失敗しました";
            }

            return Content(result);
        }
    }
}

注意した方がよさそうと思う点を以下に書いておきます。

  1. 複数回送信する場合、送信し終わったら View の JavaScript のコードにある fd をクリアして最初から始めないと、次に送信するとき前回選んだファイルも送信されダブってしまいます。上のコードはそこは未対応なので注意してください。
  2. ファイルを選択しないで送信した場合でも、上の Controller / Action Method のコードにある postedFiles は null にならないし、postedFiles.Count は 0 になりません(1 になる)。従い、上のコードでは "ファイルアップロードに失敗しました" というエラーメッセージは出ません。  
  3. View のコードにある fd.append("CustomField", "This is some extra data"); でデータを送信して Model の CustomField プロパティにバインドできます。テキストボックスを配置してユーザーに入力してもらい送信してモデルバインドすることも考えましたが、送信前に FormData オブジェクトに append するのが難しく、それは今後の検討課題です。
  4. 上の 2 に書いたファイルを選択しないで送信した場合でも postedFile が null にならず、postedFiles.Count は 1 になる理由は、内容が空のパート(下の Fiddler による要求のキャプチャ画像の 2 つ目のパート)が送信されてくるからです。普通にファイルを選択して input type="submit" ボタンクリックで送信すれば空のパートはなくなりますが、この記事のように File API と FormData を JavaScript で細工するとそれが残ってしまいます。

    想像ですが、var fd = new FormData(document.querySelector("form")); で下の画像の 2 つ目までのパートが取得され、その後の操作で 3 つ目以降が追加されていくからであろうと思います。空のパートを削除する方法は分かりませんでした。

要求ヘッダとコンテンツ

Tags: , , , ,

Upload Download

ASP.NET Core アプリの Web サーバー

by WebSurfer 29. June 2020 15:43

ASP.NET Core 3.1 の Web アプリをホストするのに利用できる Web サーバーは何で、どのような構成になるかということを調べたので、備忘録として書いておきます。

(1) 開発環境

ASP.NET Core 3.1 の Web アプリを Visual Studio Community 2019 のテンプレートを利用して作成し、ツールバーの[デバッグ(D)]⇒[デバッグの開始(S)](または[デバッグなしで開始(H)]) で Visual Studio からアプリを実行すると IIS Express が起動されますので、デフォルトでは IIS Express が使われているのは間違いなさそうです。

IIS Express

Microsoft のドキュメント「ASP.NET Core での Web サーバーの実装」によると、IIS または IIS Express を使用するとインプロセス ホスティング モデルまたはアウトプロセス ホスティング モデルのどちらかで実行されるそうです。(下図参照・・・Microsoft のドキュメントから借用しました)

インプロセス ホスティング

インプロセス ホスティング モデル

アウトプロセス ホスティング

アウトプロセス ホスティング モデル

Visual Studio から実行した場合どちらで動いているかですが「インプロセス ホスティング モデル」と思われます。

そのものズバリを書いた Microsoft の文書は見つからなかったので、関係する資料や Visual Studio のテンプレートで作ったアプリの内容を調べての想像が入ってますが、自信度は 90% ぐらいあります。根拠は以下の通りです。

根拠 1

Microsoft のドキュメント「IIS を使用した Windows での ASP.NET Core のホスト」には、以下のように、デフォルトでは「インプロセス ホスティング モデル」を使うと思える説明があります。

"既存のアプリではインプロセス ホスティングがオプトインされています。ASP.NET Core Web テンプレートでは、インプロセス ホスティング モデルが使用されます。"

"CreateDefaultBuilder では、UseIIS メソッドを呼び出し、CoreCLR を起動して IIS ワーカー プロセス(w3wp.exe または iisexpress.exe) 内のアプリをホストすることで、IServer インスタンスを追加します。"

"CreateHostBuilder (Program.cs) でホストを構築する場合は、CreateDefaultBuilder を呼び出して IIS 統合を有効にします。"

実際にテンプレートを使って作成したアプリの Program.cs は以下のようになっています。

namespace RazorApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

根拠 2

さらに、開発環境で IIS Express が使用する applicationHost.config に以下の設定があります。(場所に注意。IIS 用とは違います。詳しくは先の記事「ApplicationHost.config の場所」を見てください)

<system.webServer>
  <!-- 中略 -->
  <handlers>
    <add name="aspNetCore" path="*" verb="*"
      modules="AspNetCoreModuleV2"
      resourceType="Unspecified" />
  </handlers>
  <aspNetCore processPath="%LAUNCHER_PATH%"
    arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false"
    hostingModel="InProcess" startupTimeLimit="3600"
    requestTimeout="23:00:00" />
  <!-- 中略 -->
</system.webServer>

aspNetCore という名前の HTTP ハンドラが追加され、全ての要求に AspNetCoreModuleV2 ハンドラを使うよう設定されています。AspNetCoreModuleV2 というのは「ASP.NET Core モジュール」らしいです。

また、aspNetCore 要素に hostingModel="InProcess" とあり、「インプロセス ホスティング モデル」が指定されています。

ちなみに上の設定は IIS 用の applicationHost.config には含まれません。

根拠 3

Microsoft のドキュメント「ASP.NET Core モジュール」によると、"アウトプロセス ホスティング用にアプリを構成するには、プロジェクトファイル ( .csproj) で、<AspNetCoreHostingModel> プロパティの値を OutOfProcess に設定します" とのことです。

テンプレートで作ったアプリのプロジェクトファイルにはそのような設定はなく、デフォルトは InPorcess とのことなので、デフォルトでは「インプロセス ホスティング モデル」になるはずです。


ただし、別の Microsoft のドキュメント「ASP.NET Core への Kestrel Web サーバーの実装」には以下の記述があるのが気になります。

"ASP.NET Core プロジェクト テンプレートは既定では Kestrel を使用します。 Program.cs では、ConfigureWebHostDefaults メソッドにより UseKestrel が呼び出されます。"

これが自信度が 90% にとどまっている理由です。(笑)

上に述べた「ASP.NET Core への Kestrel Web サーバーの実装」の記述の他にも分からない点がいろいろあります。それらは以下の通りですが今後の調査課題ということで・・・

  • ASP.NET Core モジュールというのは、チュートリアル「IIS に ASP.NET Core アプリを発行する」のリンク先「現在の .NET Core ホスティング バンドルのインストーラー (直接ダウンロード)」からダウンロードされる dotnet-hosting-3.1.x-win.exe に含まれる Microsoft .NET Core 3.1.x - Windows Server Hosting のことだと思われる。でも、それをインストールする前から Visual Studio からアプリを実行できた。Visual Studio 2019 をインストールした時点で含まれていた?
  • IIS Express と IISHttpServer の間の設定は何もしてないがそれで動くのは何故? Microsoft のドキュメント「IIS を使用した Windows での ASP.NET Core のホスト」によると、ASP.NET Core モジュールがアプリの初期化を実行(Loads the CoreCLR と Calls Program.Main)すると書いてある。それだけで十分なのか?
  • そもそも、デフォルトで IIS Express を使うように設定される理由は何?
  • 開発環境で IIS Express リバースプロキシとして使わないで Kestrel に直接アクセスするように設定することはできるか?

(2) 運用環境

運用環境での Web serverについては、Microsoft のドキュメント「ASP.NET Core での Web サーバーの実装」がまとまっていて概要を理解するのに分かりやすいと思いました。

Linux 系の OS の場合は、上で紹介したドキュメントによると、要するに Nginx とか Apache をリバースプロキシに使って、Kestrel で ASP.NET Core アプリをホストするということのようです。Linux 系の OS は自分は触ったこともないので、残念ながらそれ以上詳しい話はできないです。

Windows OS の場合は、ASP.NET Core に付属している以下のサーバーを利用できるそうです。

  • Kestrel
  • IISHttpServer
  • HTTP.sys (旧称 WebListener)

運用環境で IIS によってホストする場合の説明は、Microsoft のドキュメント「IIS を使用した Windows での ASP.NET Core のホスト」が詳しいので、それを見てください。

上にも書きましたが、「インプロセス ホスティング」と「アウトプロセス ホスティング」という 2 つのモデルがあって、前者には IISHttpServer が、後者には Kestrel が使用されるそうです。

他に、HTTP.sys(IIS の HTTP Protocol Stack HTTP.sys とは違うもののようです)を使うというオプションもあるそうです。自分は勉強不足で多くは語れませんので、Microsoft のドキュメント「ASP.NET Core での HTTP.sys Web サーバーの実装」を見てください。

OS が Windows 10 Pro 64-bit の PC のローカル IIS に ASP.NET Core 3.1 アプリを発行するのは実際にやってみました。

その手順は Microsoft のチュートリアル「IIS に ASP.NET Core アプリを発行する」の通りですが、概略を以下に書いておきます。結果「インプロセス ホスティング モデル」になっていると思います。

  1. C:\WebSites2019 というフォルダ下に AspNetCoreWebSite という名前のフォルダを作成。
  2. IIS Manager を起動してそのフォルダをサイトに設定。
  3. サイトバインド設定は、種類: http, IP アドレス: 未使用の IP アドレスすべて, ポート: 80, ホスト名: www.aspnetcorewebsite.com とした。
  4. hosts ファイルに 127.0.0.1 www.aspnetcorewebsite.com と設定。
  5. VS2019 のテンプレートで自動生成される Startup.cs に HTTPS 通信を実行させるミドルウェアを適用するコード app.UseHsts(); と app.UseHttpsRedirection(); が含まれている。自分の PC のローカル IIS では HTTPS で通信できないので、そのあたりの設定を変更する必要があるかと思ったが、実際試すとそうでもなかった。 Microsoft のドキュメント Enforce HTTPS in ASP.NET Core にポートを設定しないとリダイレクトされないというようなことが書いてある。そのため?
  6. Microsoft のドキュメント「IIS を使用した Windows での ASP.NET Core のホスト」に "IIS サーバーのオプションを構成するには、IISServerOptions 用のサービス構成を ConfigureServices に含めます" と書いてあるが、テンプレートで作ったアプリにはその設定はない。設定しなければデフォルトが使われるので問題なさそう。
  7. チュートリアルの「アプリを発行および配置する」セクションに従って、上のステップで作成した C:\WebSites2019\AspNetCoreWebSite フォルダにプロジェクトを発行。IIS Manager で見た結果は下の画像のようになる。

IIS に発行された Razor ページ

  1. 以上の設定でブラウザから http://www.aspnetcorewebsite.com/ を要求すると Razor ページが表示される。 ワーカープロセスに C:\WebSites2019\AspNetCoreWebSite フォルダへの権限は何も与える必要はなかった。
  2. web.config は元のプロジェクトには含まれないが、自動生成されて配置される。aspNetCore という名前の HTTP ハンドラが追加され、全ての要求に AspNetCoreModuleV2 ハンドラを使うよう設定されている。AspNetCoreModuleV2 というのは「ASP.NET Core モジュール」らしい。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" 
          modules="AspNetCoreModuleV2" 
          resourceType="Unspecified" />
      </handlers>
      <aspNetCore processPath="dotnet" 
        arguments=".\RazorApp.dll" stdoutLogEnabled="false" 
        stdoutLogFile=".\logs\stdout" 
        hostingModel="inprocess" />
    </system.webServer>
  </location>
</configuration>
<!--ProjectGuid: 8db7ea97-35b2-4168-81d5-7c68974ce862-->

Tags: , , ,

CORE

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
                    には ValidationPropert 属性が付与されてない
                    から。なので 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
                    には ValidationPropert 属性が付与されてないから。
                    なので 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

About this blog

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

Calendar

<<  July 2020  >>
MoTuWeThFrSaSu
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar