Flash を使って一度に複数ファイルをアップロードできる SWFUpload というライブラリを ASP.NET 環境下で試してみました。いろいろ分かったことがありますので、備忘録として Flash のクッキーがらみのバグなどの注意点を書いておきます。
複数ファイルを一度にアップロードしながらプログレスも表示できるという条件で、自分の期待を満たしそうなライブラリを探してみたところ、SWFUpload と Multiple File Upload の 2 つが見つかりました。このうち、前者の SWFUpload を選んで試してみました。
なぜ SWFUpload の方を選んだかといえば、単に見た目が気に入ったからです。(笑) ただし、ASP.NET のコントロールのように Visual Studio の画面上でドラッグ&ドロップしてプロパティを設定するだけで使えるものではなく、ブラウザ側での操作にかなりの量の JavaScript のコードを書く必要があるのがちょっと重いです。でも、サンプルが提供されていますので、それをそのまま転用すればさほど面倒ではありません。
後者の Multiple File Upload の方は、ドキュメントを読んだ限りですが、ASP.NET 向けに特化されているようです。SWFUpload と同等な機能を ASP.NET のカスタムコントロールにラップして、ASP.NET に慣れたプログラマにとって使いやすくしているような感じです。
上の画像のアプリは、SWFUpload のサンプルの SimpleDemo と applicationdemo.net を合体して作ったものです。applicationdemo.net には、アップロードされた jpg ファイルからサーバー側のプログラムで Thumbnail を作って Session に保持し、クライアント側の JavaScript で動的に img タグを作ってそれに表示するコードが含まれています。
実際のアプリでは、Thumbnail を作って Session に保持する代わりに、サーバーのフォルダや DB に保存することになります。そのサンプルは提供されていませんが、従来の ASP.NET コントロールの FileUpload を使った場合と同等な処置となるので、ASP.NET アプリ開発経験者には簡単に実装できるはずです。
アプリの作り方を簡単に書いておきます。
-
Visual Studio で、新規に ASP.NET Web サイトプロジェクトを作ります。
-
サンプルを Google のサイト からダウンロードします。現時点での最新版は SWFUpload v2.2.0.1 Samples.zip です。demos というフォルダの中に多数のサンプルが含まれています。
-
demos\simpledemo フォルダの index.php が参照している .css, .js, .swf ファイルを使用しますので、適当なフォルダ(例: css, Scripts, Swfupload)をアプリケーションルート設けて、そこに参照されているファイルをコピーしてください。同名のファイルが複数含まれていますので、index.php が参照しているファイルのフォルダに注意してください。
-
handlers.js に Thumbnail 書き込みのための JavaScript コードを追加します。上の手順でコピーした handlers.js を Visual Studio で開き、そこに demos\applicationdemo.net\js\handlers.js の addImage, fadeIn メソッドを丸ごとコピーします。さらに、uploadSuccess メソッドに addImage("thumbnail.aspx?id=" + serverData); を追加します。下の例を参考にしてください。
-
アプリケーションルートに demos\applicationdemo.net フォルダの Global.asax, thumbnail.aspx, thumbnail.aspx.cs, upload.aspx, upload.aspx.cs をコピーしてください。
-
アプリケーションルートに App_Code フォルダを作成し、demos\applicationdemo.net\App_Code フォルダの Thumbnail.cs をコピーしてください。
-
アプリケーションルートに images フォルダを作成し、demos\images, demos\simpledemo\images, demos\applicationdemo.net\images フォルダの画像ファイルをコピーしてください。default.css を開いて、画像の url(3 ヶ所あります)を修正してください。
-
Default.aspx, Default.aspx.cs, web.config は、ASP.NET の Web サイトプロジェクトを作った際に自動生成されたものに手を加えて使用します。ここまでで Web サイトプロジェクトの構成は上のソリューションエクスプローラの画像のようになっているはずです。
-
大きなファイルをアップロードする場合は、web.config に httpRuntime 要素の設定変更が必要です。設定例は、samples\asp.net フォルダの web.config にあります。加えて、IIS7 では applicationHost.config の requestFiltering 要素の設定も要注意です。
-
続いて、Deafult.aspx.cs の Page_Load イベントハンドラに Session.Clear(); を追加します。
-
Deafult.aspx に index.php のコードを移植します。その際、 Flash のパラメータ flash_url, post_params を修正し、Thumbnail 画像を格納する div タグを追加します。ヘッダーのロゴのリンク先も適宜変更してください。下の例を参考にしてください。
handler.js(一部のみ)
function uploadSuccess(file, serverData) {
try {
// この行追加
addImage("thumbnail.aspx?id=" + serverData);
var progress =
new FileProgress(file, this.customSettings.progressTarget);
progress.setComplete();
progress.setStatus("Complete.");
progress.toggleCancel(false);
} catch (ex) {
this.debug(ex);
}
}
// 以下の function 2 つを追加
function addImage(src) {
var newImg = document.createElement("img");
newImg.style.margin = "5px";
・・・中略・・・
}
function fadeIn(element, opacity) {
var reduceOpacityBy = 5;
var rate = 30; // 15 fps
・・・中略・・・
}
Default.aspx.cs
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Clear the user's session
Session.Clear();
}
}
Default.aspx
<%@ Page Language="C#"
AutoEventWireup="true"
CodeFile="Default.aspx.cs"
Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>SWFUpload Demos - Simple Demo</title>
<link href="css/default.css" rel="stylesheet" type="text/css" />
<script src="Swfupload/swfupload.js" type="text/javascript"></script>
<script src="Scripts/swfupload.queue.js" type="text/javascript"></script>
<script src="Scripts/fileprogress.js" type="text/javascript"></script>
<script src="Scripts/handlers.js" type="text/javascript"></script>
<script type="text/javascript">
var swfu;
window.onload = function() {
var settings = {
flash_url: "swfupload/swfupload.swf",
// アップロードされたファイルをサーバーで保存するなどの
// 処置(このサンプルでは Thumbnail の表示)を行うページ
// を指定する。
upload_url: "upload.aspx",
// Flash のバグによるセッション切れ、Forms 認証切れ問題
// の対応。セッションクッキーと Forms 認証クッキーを、そ
// れぞれ ASPSESSID, AUTHID という名前でフォームに含めて
// 送信する。Global.asax の BeginRequest ハンドラでそれら
// を取得して、応答クッキーを書き換える。
post_params: {
"ASPSESSID": "<%=Session.SessionID %>",
"AUTHID": "<%=Request.Cookies[FormsAuthentication.FormsCookieName] == null? String.Empty : Request.Cookies[FormsAuthentication.FormsCookieName].Value %>"
},
file_size_limit: "100 MB",
file_types: "*.*",
file_types_description: "All Files",
file_upload_limit: 100,
file_queue_limit: 0,
custom_settings: {
progressTarget: "fsUploadProgress",
cancelButtonId: "btnCancel"
},
debug: false,
// Button settings
button_image_url: "images/TestImageNoText_65x29.png",
button_width: "65",
button_height: "29",
button_placeholder_id: "spanButtonPlaceHolder",
button_text: '<span class="theFont">Hello</span>',
button_text_style: ".theFont { font-size: 16; }",
button_text_left_padding: 12,
button_text_top_padding: 3,
// The event handler functions are defined in handlers.js
file_queued_handler: fileQueued,
file_queue_error_handler: fileQueueError,
file_dialog_complete_handler: fileDialogComplete,
upload_start_handler: uploadStart,
upload_progress_handler: uploadProgress,
upload_error_handler: uploadError,
upload_success_handler: uploadSuccess,
upload_complete_handler: uploadComplete,
// Queue plugin event
queue_complete_handler: queueComplete
};
swfu = new SWFUpload(settings);
};
</script>
</head>
<body>
<form id="form1" runat="server">
<div id="header">
<h1 id="logo"><a href="index.html">SWFUpload</a></h1>
<div id="version">v2.2.0</div>
</div>
<div id="content">
<h2>Simple Demo</h2>
<p>This page demonstrates a simple usage of SWFUpload.
It uses the Queue Plugin to simplify uploading or
cancelling all queued files.</p>
<div class="fieldset flash" id="fsUploadProgress">
<span class="legend">Upload Queue</span>
</div>
<div id="divStatus">0 Files Uploaded</div>
<div>
<span id="spanButtonPlaceHolder"></span>
<input id="btnCancel"
type="button"
value="Cancel All Uploads"
onclick="swfu.cancelQueue();"
disabled="disabled"
style="margin-left: 2px; font-size: 8pt; height: 29px;" />
<%--Thumbnail 画像表示のため追加--%>
<div id="thumbnails"></div>
</div>
</div>
</form>
</body>
</html>
以上で Web アプリは動くはずです。[Hello]ボタンをクリックするとファイルの選択ダイアログが開くので、jpg ファイルを選択して(複数選択可)、ダイアログの[開く(O)]ボタンをクリックすれば選択したファイルがアップロードされ、upload.aspx で処置されて、上の画像のような Thumbnail が表示されます。
問題の Flash のバグの話を以下に書きます。Firefox, Chrome, Safari など、IE 以外のブラウザを使用した場合は Flash がページを要求する際、クッキーが送られないという問題があります。
Flash が起動されている状態でも、HyperLink クリックでページを要求した場合や、ブラウザのアドレスバーに URL を打ち込んで要求する場合はクッキーは送られます。問題は、あくまで Flash がページを要求する場合ということに注意してください。今回の例では、Flash が flash_url パラメータに設定されている upload.aspx を要求する場合がそれに該当します。
当然ながら、クッキーが送られないとセッションが維持できません。今回の例では、Session に Thumbnail 画像を保持しているので、要求にクッキーが含まれないとセッションが維持できず、アップロード後に画像の Thumbnail を表示するところがうまくいきません。
ファイルがアップロードされると、Flash はパラメータ upload_url で指定されている upload.aspx を自動的に要求しますが、このときクッキーは送信されません。それ故、サーバー側で正しく Session が取得できず、別の Session に Thumbnail を書き込んでしまいます。結果、Thumbnail 画像は表示されません。
回避策は、post_params に "ASPSESSID": "<%=Session.SessionID %>" を設定し(こうすることにより、ブラウザはフォーム変数にセッション ID を含めて送信します)、Global.asax の BeginRequest イベントのハンドラでフォーム変数からセッション ID を取得し、それでクッキーを書き換えてやることです。
いままで作成したコードに回避策が折り込み済みです。なので、Thumbnail は問題なく表示されます。Default.aspx の post_params の設定と、Global.asax の BeginRequest ハンドラを見てください。
Forms 認証を行う場合も、同様に、Flash のバグによる認証クッキー切れの問題があります。
例えば、Forms 認証を設定し、login.aspx ページを設け、ルートの web.config で deny user="?" として全ページ(upload.aspx を含め)匿名アクセス不可とした場合を考えます。ログインすれば Default.aspx は表示され、アップロードするファイルを選択するところまでは行きます。ところが、いざアップロードしようとすると HTTP 302 が返されてアップロードに失敗します。これは、Flash が upload.aspx を要求したとき、認証クッキーが送られてこないので、login.aspx にリダイレクトされてしまうからです。
この問題は、セッションクッキーの場合と同様な方法で、post_params で認証クッキーをフォーム変数に含めて送るように設定し、Global.asax の BeginRequest ハンドラで認証クッキーを書き換えることで回避できます。この回避策もいままで作成したコードに折り込み済みです。
Default.aspx の post_params の設定と、Global.asax の BeginRequest ハンドラを見てください。この対応で、Flash のバグによるセッションクッキーと Forms 認証クッキーが切れる問題は回避でき、ASP.NET 環境で SWFUpload を使えるようになるはずです。
なお、くどいようですが、Flash のバグによるクッキー切れの問題は、Firefox, Chrome, Safari など、IE 以外のブラウザを使用した場合のみですので注意してください。