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 に関して参考になると思われる(と言うか自分が参考にした)記事へのリンクを張っておきます。