きよくらの備忘録

「三日坊主と呼ばせない!日記」改め。主にソフトウェア開発関連の話題。

ModalDialogでポストバックしてファイルをダウンロードさせるTIPS

とりあえずIEで検証。Firefox3とかSafariは未検証なのであしからず。

一般的かどうかに自信は無いですが、ASP.NETにて、ポストバックしたあとにサーバ側でファイルを読み込み、それをダウンロードさせるときは以下のようなコードを書くと思います。

using System;
using System.IO;

namespace DownloadTest
{
    public partial class DialogWindow : System.Web.UI.Page
    {

        /// <summary>
        /// Excelファイルを読み込んでダウンロードさせる
        /// </summary>
        protected void btnDonwload_Click(object sender, EventArgs e)
        {
            byte[] bin=null;
            using (FileStream fs = new FileStream(Server.MapPath("Data/Data.xlsx"), FileMode.Open))
            {
                using (BinaryReader binReader = new BinaryReader(fs))
                {
                    bin = binReader.ReadBytes((Int32)fs.Length);
                }
            }
            Response.Clear();
            Response.ContentType = "application/octet-stream";
            Response.AddHeader("Content-Disposition", " attachment;filename=Data.xlsx");
            Response.BinaryWrite(bin);
            Response.End();
        }
    }
}

通常はこうすることで、ファイルの保存確認ダイアログをブラウザに表示することが出来るのですが、showModalDialogで表示させたモーダルダイアログで同じ事をやろうとすると、以下の現象が発生します。

  • IE7
    ファイル確認ダイアログが表示されると共にIEの別ウィンドウが表示される。
    ファイル確認ダイアログでの操作が終了すると、開いたウィンドウは自動的に閉じる。
  • IE6
    ファイル確認ダイアログが表示されると共にIEの別ウィンドウが表示される。
    ファイル確認ダイアログでの操作が終了しても、開いたウィンドウは自動的に閉じない

IE7での動作については、まあ許容範囲かなとも思えるのですが、IE6の場合は場合によってはNGをいただく場合もあります。

なんでこんな感じになるかというと、modalDialogの場合にポスト(およびリンク)のtargetが規定で別ウィンドウになっているからだ、と推測されます。その為、ModalDialogでポストバックを行う場合には、base要素でtarget属性を_selfに指定するのが暗黙の前提になっていると思います*1

では、このケースでもtargetを_selfに指定してみるとどうなるかというと……。動きません(ToT)。サーバ側にリクエストは飛び、レスポンスも返しているのですが、dialog側で読み捨てているような感じです。


で、これをどうにか解決しようと思って考えてみたのが以下の方法。

  1. frameを一つだけ持ったframesetのページ一つ用意する
  2. 本来dialog内に表示したいページを上記のframeのソースに指定する

……以上。
dialog内でもframeは表示可能で、かつ、frame内ではdialogの動きの縛りから解放されて普通のwindowと同じように動くので、それを利用したという感じです。
実際のソースは以下。

・[DialogWindow.aspx] 本来dialog内に表示したいページ

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="DialogWindow.aspx.cs" Inherits="DownloadTest.DialogWindow" %>
<!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>無題のページ</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:Button ID="btnDonwload" runat="server" Text="Download" OnClick="btnDonwload_Click" />
        <input type="button" value="閉じる" onclick="javascript:parent.returnValue=0;parent.close();" />
    </div>
    </form>
</body>
</html>

・[DialogFrame.aspx] 今回の小細工(^^;の為に用意した、framesetのページ

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="DialogFrame.aspx.cs" Inherits="DownloadTest.DialogFrame" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>無題のページ</title>
    <script type="text/javascript">
        retValue = null; // 戻値の初期化。ダイアログの×ボタンで閉じられた時用。
    </script>
</head>
<frameset cols="1">
    <frame src="DialogWindow.aspx" />
</frameset>
</html>

・[Default.aspx] dialogを呼び出すページ

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DownloadTest._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 id="Head1" runat="server">
    <title>無題のページ</title>
    <script type="text/javascript">
    function OpenDialog(){
        var ret = showModalDialog('DialogFrame.aspx');
        alert(ret);
    }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <input type="button" value="Open Dialog" onclick="javascript:OpenDialog();" />
    </div>
    </form>
</body>
</html>

ちなみに、最初に提示したcsファイルは、今回の場合はDialogWindow.aspxのコードビハインドファイルですね。また、上記サンプルではshowModalDialogの戻り値もframe内のページから操作出来ることを確認するために、DialogWindow.aspxからparentの(DialogFrame.aspxの)retValueに値を設定、戻った後のDefault.aspxで表示してみています*2

framesetのaspxファイルは本当にガワだけなので、Visual Studioのデザイナで扱うときもストレス無く作業出来ると思います。

……ので、どうしてもshowModalDialogを使う必要があって、どうしてもそこからファイルをダウンロードさせないといけないときは、使える手では無いかと思います。

*1:これは言い過ぎかもしれませんが

*2:と言うことで、戻り値操作やスクリプトからのダイアログのクローズも問題無く可能