WebSurfer's Home

Filter by APML

SWFUpload を使ってみました

by WebSurfer 3. November 2010 15:45
SWFUpload ライブラリを使ってみました

Flash を使って一度に複数ファイルをアップロードできる SWFUpload というライブラリを ASP.NET 環境下で試してみました。いろいろ分かったことがありますので、備忘録として Flash のクッキーがらみのバグなどの注意点を書いておきます。

複数ファイルを一度にアップロードしながらプログレスも表示できるという条件で、自分の期待を満たしそうなライブラリを探してみたところ、SWFUploadMultiple 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 アプリ開発経験者には簡単に実装できるはずです。

アプリの作り方を簡単に書いておきます。

ソリューションエクスプローラ
  1. Visual Studio で、新規に ASP.NET Web サイトプロジェクトを作ります。
  2. サンプルを Google のサイト からダウンロードします。現時点での最新版は SWFUpload v2.2.0.1 Samples.zip です。demos というフォルダの中に多数のサンプルが含まれています。
  3. demos\simpledemo フォルダの index.php が参照している .css, .js, .swf ファイルを使用しますので、適当なフォルダ(例: css, Scripts, Swfupload)をアプリケーションルート設けて、そこに参照されているファイルをコピーしてください。同名のファイルが複数含まれていますので、index.php が参照しているファイルのフォルダに注意してください。
  4. handlers.js に Thumbnail 書き込みのための JavaScript コードを追加します。上の手順でコピーした handlers.js を Visual Studio で開き、そこに demos\applicationdemo.net\js\handlers.js の addImage, fadeIn メソッドを丸ごとコピーします。さらに、uploadSuccess メソッドに addImage("thumbnail.aspx?id=" + serverData); を追加します。下の例を参考にしてください。
  5. アプリケーションルートに demos\applicationdemo.net フォルダの Global.asax, thumbnail.aspx, thumbnail.aspx.cs, upload.aspx, upload.aspx.cs をコピーしてください。
  6. アプリケーションルートに App_Code フォルダを作成し、demos\applicationdemo.net\App_Code フォルダの Thumbnail.cs をコピーしてください。
  7. アプリケーションルートに images フォルダを作成し、demos\images, demos\simpledemo\images, demos\applicationdemo.net\images フォルダの画像ファイルをコピーしてください。default.css を開いて、画像の url(3 ヶ所あります)を修正してください。
  8. Default.aspx, Default.aspx.cs, web.config は、ASP.NET の Web サイトプロジェクトを作った際に自動生成されたものに手を加えて使用します。ここまでで Web サイトプロジェクトの構成は上のソリューションエクスプローラの画像のようになっているはずです。
  9. 大きなファイルをアップロードする場合は、web.config に httpRuntime 要素の設定変更が必要です。設定例は、samples\asp.net フォルダの web.config にあります。加えて、IIS7 では applicationHost.config の requestFiltering 要素の設定も要注意です。
  10. 続いて、Deafult.aspx.cs の Page_Load イベントハンドラに Session.Clear(); を追加します。
  11. 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 以外のブラウザを使用した場合のみですので注意してください。

Tags: , ,

Upload Download

ダウンロードの際の拡張子 と MIME Type の指定

by WebSurfer 2. September 2010 22:42

MSDN フォーラムで、IE で xlsm をダウンロードすると zip 形式になってしまい、直接 Excel で開けないという話があったので、ちょっと調べてみました。

結局、単にそれはサーバー側の Web アプリの問題だったのですが、調査の結果、自分が知らなかったことがいろいろあったので、忘れないように書いておきます。

ブラウザは、ヘッダーの Content-Disposition: attachment; filename= で指定する拡張子と、Content-Type: で指定する MIME Type のどちらでファイルの種類を判断するでしょうか?

検証した結果は以下のとおりでした。簡単に言うと、IE8 は拡張子で判断、Opera は MIME Type で判断、Forefox は拡張子と MINE Type の指定が違う場合結果は未定義のようです。

ブラウザ Ext: xlsm
MT: xlsm
Ext: xlsm
MT: zip
Ext: zip
MT: zip
Ext: zip
MT: xlsm
IE8 Excel Excel Zip Zip
Firefox 3.6.8 Excel Zip Zip Zip
Opera 10.61 Excel Zip Zip Excel

上の表で、Ext は Content-Disposition に設定した拡張子、MT は Content-Type に設定した MIME Type です。

検証に使ったファイルは、有効な xlsm 形式のファイルで、拡張子は正しく xlsm としており、検証の間、中身も名前も一切変えていません。

クライアントの OS は Vista SP2 です。拡張子とアプリケーションの関連付けは、xlms は Excel、zip は WinZip としています。Web サーバーは Windows Server 2008 の IIS7, ASP.NET 3.5 SP1 です。

予想外の動きをしたのは Opera で、拡張子と MIME Type が異なる場合、拡張子を MIME Type に合わせて書き換えてしまいます。下の画像は、拡張子を xlsm、MIME Type を zip とした場合の例です。元のファイル名 001.xlsm が 001.zip に書き換えられています。

Opera のファイルダウンロード時のダイアログ

直リンクの場合(a 要素の href 属性にサーバーに置いた xlsm ファイルを指定した場合)、ヘッダーは以下のようになります。ブラウザは正しく判断出来るようです。

Content-Type: application/vnd.ms-excel.sheet.macroEnabled.12
Last-Modified: Thu, 02 Sep 2010 13:07:58 GMT
Accept-Ranges: bytes
ETag: "3e8647dd9f4acb1:0"
X-Powered-By: ASP.NET
Date: Thu, 02 Sep 2010 13:12:12 GMT
Content-Length: 12477

ちなみに、HTTP ハンドラーを使った場合のヘッダーは以下のようになります。

Cache-Control: private
Content-Type: application/vnd.ms-excel.sheet.macroEnabled.12
Content-Disposition: attachment; filename=001.xlsm
X-AspNet-Version: 2.0.50727
X-Powered-By: ASP.NET
Date: Thu, 02 Sep 2010 13:21:00 GMT
Content-Length: 12477

検証に使った HTTP ハンドラのコードを参考までにアップしておきます。

<%@ WebHandler Language="C#" Class="_045_Handler" %>

using System;
using System.Web;
using System.IO;

public class _045_Handler : IHttpHandler 
{
  public void ProcessRequest(HttpContext context)
  {
    string path = 
      context.Server.MapPath("~/Test/data/001.xlsm");

    if (!File.Exists(path))
    {
      return;
    }
        
    string type = context.Request.QueryString["type"];

    switch (type)
    {
      case "xlsm_xlsm":
        SetHeader(context, "001.xlsm", 
          "application/vnd.ms-excel.sheet.macroEnabled.12");
        break;
      case "xlsm_zip":
        SetHeader(context, "001.xlsm", 
          "application/x-zip-compressed");
        break;
      case "zip_zip":
        SetHeader(context, "001.zip", 
          "application/x-zip-compressed");
        break;
      case "zip_xlsm":
        SetHeader(context, "001.zip", 
          "application/vnd.ms-excel.sheet.macroEnabled.12");
        break;
      default:
        SetHeader(context, "001.xlsm", 
          "application/vnd.ms-excel.sheet.macroEnabled.12");
        break;                
    }
    context.Response.TransmitFile(path);
    context.Response.End();
  }
    
  public bool IsReusable
  {
    get {
      return false;
    }
  }

  protected void SetHeader(HttpContext context, string name, string type)
  {
    context.Response.AppendHeader("Content-Disposition",
      "attachment; filename=" + name);
    context.Response.ContentType = type;
  }
}

他に、今回知った新(?)事実は、Excel 2007 からファイル形式が変わったということです。何をいまさらと言われるかも知れませんが。(汗)

Excel 2007 からはファイルはバイナリではなくなり、xml 形式のテキストを zip で圧縮したものになっているそうです。確かに WinZip で xlsm ファイルを開くことができ、中身は xml 形式のテキストファイルでした。

また、xlsm は「マクロ有効ブック」というそうで、セキュリティ対策として、拡張子からマクロが含まれていることが分かるような名前の規則を作ったそうです。

------------ 2011/5/21 追記 ------------

IE8 は拡張子で判断と書きましたが、やはり IE はそのような仕様になっているようです。以下のページを参考にしてください。

ファイルのダウンロードダイアログで表示されるファイル名の命名規則

即ち、上のページの「詳細」の 1 番目に書いてあるとおり「Content-Disposition: ヘッダが存在する場合は、filename パラメータで設定されたファイル名が利用されます。」ということです。

サポートページには書いてないですが、Content-Disposition: ヘッダでファイル名が指定してあれば、Content-Type: の指定は無視されます。

2 番目の「HTTP レスポンス内に存在する、送られてきた Content-Type が、レジストリの HKEY_CLASS_ROOT\MIME\Database\Content Type 以下に存在するかどうか・・・」というのは、Content-Disposition: ヘッダの filename パラメータにファイル名の指定がない場合に限るようです。

Tags: ,

Upload Download

FileUpload が無反応

by WebSurfer 1. September 2010 14:03

FileUpload コントロールのテストをしている時、テキストボックスに文字を入力してボタンをクリックしても、入力内容によっては何の反応もないことがありました。

Fileupload

ボタンは単純に input type="submit" なので、クリックすれば、必ず form が submit されるはずなのですが・・・

何故でしょう?

不思議に思っていたんですが、MSDN フォーラムを見ていたら分かりました。XP SP2 以降に取られたセキュリティ対策だそうです。

詳しくは、マイクロソフトサポートオンラインの 文書番号:890981 に書いてありますが、ローカルや共有フォルダのファイルの完全修飾パスでないと、ファイルの送信ができないよう仕様が変更されたそうです。

ただし、あまり厳密なものではなく、それらしい形式なら submit はかかります。例えば、c は無反応ですが、c: なら OK といった具合です。

そんな程度でセキュリティ対策に効果があるのか、よく分かりません。(汗)

ちなみに、Firefox 3.6.8 の場合は、テキストボックスにフォーカスを当てると「ファイルの選択」ダイアログが開きます。要するに、テキストボックスに直接入力できないようになっています。セキュリティ対策としては、こちらの方が効果がありそうな気がします。

Tags:

Upload Download

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。ブログ2はそれ以外の日々の出来事などのトピックスになっています。

Calendar

<<  December 2025  >>
MoTuWeThFrSaSu
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar