WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

FileUpload が無反応

by WebSurfer 2010年9月1日 14:03

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

Fileupload

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

何故でしょう?

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

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

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

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

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

Tags:

Upload Download

ダウンロードしたファイルの更新日時

by WebSurfer 2010年8月30日 16:21

クライアントがサーバーからファイルをダウンロードし、自分の HDD などに保存した場合、そのファイルの更新日時は HDD に保存した日時になります。

ファイルのダウンロード

これをサーバー側で設定した日付にすることはできるでしょうか? ActiveX などのプラグインは使わないという条件でです。

結論から言えば、世間一般に使われている解凍ツールと互換性のある zip アーカイブを使えば可能です。

サーバーからは zip アーカイブに更新日時情報を含めて送信し、クライアント側で解凍して保存する時に zip アーカイブに含まれている更新日時に設定してもらうということです。

Web アプリケーションとしては、以下ような手順になります。

  1. データベースまたはファイルシステムからバイト列データを取り出す。
  2. 既存のライブラリを利用して zip アーカイブをバイト列として作成。更新日時やファイル名はその時設定。
  3. 作成したバイト列を Response.BinaryWrite メソッドで HTTP 出力ストリームに書き込み

参考に、フリーのライブラリ SharpZipLib を使った例をアップしておきます。実行するには Bin フォルダに ICSharpCode.SharpZipLib.dll を入れておく必要があります。下記のサイトから入手できます。

The Zip, GZip, BZip2 and Tar Implementation For .NET

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Configuration" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="ICSharpCode.SharpZipLib.Zip" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  protected void Page_Load(object sender, EventArgs e)
  {
    // 例として、SQL Server DB からデータを取得する
    // ことで考えています。
    ConnectionStringSettings cs =
      ConfigurationManager.ConnectionStrings["MyDB"];
    string connString = cs.ConnectionString;
    SqlConnection connection = new SqlConnection(connString);
    string query = 
      "SELECT filename, data FROM files WHERE id=@id";
    SqlCommand command = new SqlCommand(query, connection);
    command.Parameters.AddWithValue("@id", 
      Convert.ToInt32(Request.QueryString["id"]));
    string filename = null;
    byte[] data = null;
       
    try
    {
      connection.Open();
      SqlDataReader reader = command.ExecuteReader();

      if (reader.Read())
      {
        filename = (string)reader[0];
        data = (byte[])reader[1];
      }
    }
    finally
    {
      connection.Close();
    }

    if (filename == null || data == null)
    {
      // DB から取得できなかった場合の処置。
      // ここでは単に return
      return;
    }

    Response.AppendHeader("Content-Disposition",
      "attachment; filename=" + 
      HttpUtility.UrlEncode(filename) + ".zip");
    Response.ContentType = "application/x-zip-compressed";

    // 何故かバイト列のサイズを指定した方が圧縮率
    // が高くなる。圧縮前よりは小さくなるはずなの
    // で、とりあえず data.Length とする。
    byte[] zippedData = new byte[data.Length];
    int zippedDataLength = 0;
    using (MemoryStream memoryStream = 
      new MemoryStream(zippedData))
    {
      using (ZipOutputStream zipOutStream = 
        new ZipOutputStream(memoryStream))
      {
        // 圧縮度の設定。9 が最高
        zipOutStream.SetLevel(9);

        ZipEntry entry = new ZipEntry(filename);

        // ここで更新日時を設定
        entry.DateTime = new DateTime(2000, 1, 1);

        zipOutStream.PutNextEntry(entry);
        zipOutStream.Write(data, 0, data.Length);               
        zipOutStream.Finish();

        // 圧縮後のサイズを取得。Finish() の前に置く
        // のは NG。zipOutStream.Length は NG。
        zippedDataLength = (int)zipOutStream.Position;

        zipOutStream.Close();
      }
    }       
    Array.Resize(ref zippedData, zippedDataLength);
    Response.BinaryWrite(zippedData);
    Response.End();
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>

そもそも、ファイル名、更新日時その他のメタデータはファイルシステムが保持しているもので、ファイル本体には含まれていません。

ファイル名のみは、ブラウザが自動的に attachment; filename= に指定された名前で保存してくれますが(日本語の文字化けの問題はありますが)、その他の情報はダメなようです。

Content-Disposition ヘッダーフィールドにファイル名、作成日時、更新日時等の情報を追加してクライアントに送ることは可能です。でもそれは送る手段が用意されているというだけで、ブラウザが自動的にそれに書き換えてくれることはありません。

Tags: , ,

Upload Download

ダウンロードは別ウィンドウで

by WebSurfer 2010年8月6日 20:52

ファイルをダウンロードする際、ダウンロード後にも何かの処置を継続し、その結果を表示したい場合はどうすればいいのでしょう?

例えば、Button をクリックするとポストバックがかかり、その Click イベントのハンドラでファイルをダウンロードし、その後そのページを継続して表示するが、ダウンロード後は表示したくない部分がある場合を考えて見ます。

ファイルダウンロード後ページの一部を隠す

ダウンロード後は表示したくない部分を Panel に入れて、Click イベントで Panel.Visible プロパティを false に設定するという手段を考えると思います。

しかしながら、ファイルのダウンロードとその処置を 1 ページで行うとうまくいきません。

何故なら、ボタンがクリックされてポストバックがかかり、サーバーから送られてくるのは HTTP 応答ヘッダとダウンロードされるファイルのバイト列だけだからです。

ボタンクリック後もポスト前の画面が表示されているのは、ブラウザにポスト前の画面が保持されているからで(ここのところ確証はないのですが)、ポストバック後サーバーから送られてきたものが表示されているわけではありません。前の画面だから、Panel の中身は表示されたままです。

Response.End(), Response.Flush() などをしなかったら、Download も処置の継続もうまくのではと思って、いろいろ試してみましたが、1 ページで処置するのは無理でした。

というわけで、ファイルをダウンロードする別のページを作って、それを呼び出すのがよさそうです。例えば、以下のような感じです。

<%@ 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">
  protected void Button1_Click(object sender, EventArgs e)
  {
     Panel1.Visible = false;
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>ファイルダウンロード</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>ファイルダウンロード後に以下の部分を隠す</h1>
    <asp:Panel ID="Panel1" runat="server">
      <div style="background-color: Silver">
        <h2>隠す部分</h2>
        <asp:Button ID="Button1" 
          runat="server" 
          Text="Download" 
          onclick="Button1_Click" 
          OnClientClick="window.open('Download.aspx', null);" />
     </div>
   </asp:Panel>    
  </div>
  </form>
</body>
</html>

ダウンロードを行う別ページは、何をどのようにダウンロードするかによって千差万別ですが、DB からデータを取得して CSV ファイルにしてダウンロードする場合の例を、ついでにアップしておきます。

この例では、デリミタに使っている改行やコンマがフィールドに含まれているとうまくいませんので注意してください。

<%@ Page Language="C#" ContentType="application/octet-stream" 
ResponseEncoding="Shift_JIS" %>
<%@ Import Namespace="System.Data.SqlClient" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

  protected void Page_Load(object sender, EventArgs e)
  {        
    string connString = 
      ConfigurationManager.ConnectionStrings["Pubs"].ConnectionString;
    string query = "SELECT * FROM employee";
    SqlConnection sqlConn = new SqlConnection(connString);
    SqlCommand sqlCmd = new SqlCommand(query, sqlConn);
    string csvString = String.Empty;
    try
    {
      sqlConn.Open();
      SqlDataReader reader = sqlCmd.ExecuteReader();
      while (reader.Read())
      {
        for (int i = 0; i < reader.FieldCount; i++)
        {
          if (i == reader.FieldCount - 1)
          {
            csvString += reader[i].ToString() + "\r\n";
          }
          else
          {
            csvString += reader[i].ToString() + ",";
          }
        }
      }
    }
    finally
    {
      sqlConn.Close();
    }

    Encoding encode = Encoding.GetEncoding("shift_jis");
    Response.AppendHeader("Content-Disposition", "attachment; filename=test.csv");
    Response.Write("Employee ID,First Name,Middle Initial,Last Name,Job ID,Job Level,Pub ID,Hire Date\r\n");
    Response.BinaryWrite(encode.GetBytes(csvString));
    Response.End();
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>無題のページ</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>    
  </div>
  </form>
</body>
</html>

なお、IE8 の場合、空白のウィンドウが一旦開いてしまい、「ファイルのダウンロード」ダイアログのボタンをクリックするまで閉じないという気に入らない点があります。

今のところ回避策が見つかっていません。回避策をご存知の方はアドバイスいただけるとうれしいです。

Tags: ,

Upload Download

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar