WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

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

by WebSurfer 6. August 2010 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

Web Custom Control の例外処置

by WebSurfer 6. August 2010 17:26

Web カスタムコントロールでは、Visual Studio でプロパティの初期値を設定する際、入力値をチェックして条件に合わなければ例外をスローするようにできます。以下の画像のような感じです。

Web カスタムコントロールの例外処置

具体的には、例えば以下のように、値の設定時に入力値をチェックして条件に合わなければ例外をスローするようにコーディングしたとします。

namespace CustomControlThrowExceptionTest
{
  public class MyControl : WebControl
  {
    public decimal MaxValue
    {
      get
      {
        object obj = ViewState["MaxValue"];
        return (obj != null) ? (decimal)obj : Decimal.MaxValue;
      }
      set
      {
        if (value < MinValue)
        {
          throw new Exception("MaxValueException");
        }
        ViewState["MaxValue"] = value;
      }
    }

    public decimal MinValue
    {
      get
      {
         object obj = ViewState["MinValue"];
         return (obj != null) ? (decimal)obj : Decimal.MinValue;
      }
      set
      {
        if (MaxValue < value)
        {
          throw new Exception("MinValueException");
        }
        ViewState["MinValue"] = value;
      }
    }
    ・・・中略・・・
  }
}

上記のプロパティを含む Web カスタムコントロールを作って、それをページに配置します。

そして、Visual Studio のプロパティウィンドウで、例えば、まず MaxValue を 100 に設定し、次に MinValue に 200 を入力すると、上の画像のようなダイアログが現れて設定の誤りを知らせてくれます。

コンパイルして Bin フォルダに配置しても、App_Code フォルダにソースのままファイルを置いても同じ動作をします(条件をチェックして例外をスローします)。

Web ユーザーコントロールの場合は、チェックは実行時でないとできません(間違った初期値が設定できてしまいます)。

知ってました? こんなことは常識と言われるかもしれませんが、それを知った当時、Web ユーザーコントロールしか知らない未熟者だった自分には驚きでした。

Tags:

Web Custom Control

SQL Server の IN 演算子の利用

by WebSurfer 5. August 2010 12:31

DB のテーブルから一つの項目を選び、その項目の詳細を表示するというケースはよくあると思います。そのようなサンプルは Web から容易に見つかると思います。

では、一度に複数の項目を選んで、複数の項目の詳細を同時に表示するにはどうすればいいでしょうか?

SQL Server には IN 演算子というものがあって、それを利用するのがよさそうです。以下は SQL Server 2005 Online Book のサンプルです。

SELECT FirstName, LastName, e.Title
FROM HumanResources.Employee AS e
    JOIN Person.Contact AS c 
    ON e.ContactID = c.ContactID
WHERE e.Title IN ('Design Engineer', 'Tool Designer', 'Marketing Assistant');

ASP.NET のアプリでは、CheckBoxList で複数の項目を選んで、Button クリックで GridView に選んだ項目の詳細を表示することを考えます。

ユーザーの選択結果によって動的にクエリ(上記で言うと、IN (...) の ... の部分)を変える必要があります。

他にもっとスマートなやり方があるかもしれませんが、プログラムで SELECT クエリを組み立てて処置するサンプルを書いておきます。

DB は Microsoft が無償提供している Northwind サンプルデータベースの Orders テーブルを使用しました。

<%@ 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)
  {
    string selectedItems = "(";
    int count = 0;

    for (int i = 0; i < CheckBoxList1.Items.Count; i++)
    {
      if (CheckBoxList1.Items[i].Selected)
      {
        selectedItems += CheckBoxList1.Items[i].Value + ", ";
        count++;
      }
    }

    if (count > 0)
    {
      selectedItems = 
        selectedItems.TrimEnd(new char[] { ',', ' ' });
      selectedItems += ")";
      SqlDataSource2.SelectParameters.Clear();
      SqlDataSource2.SelectCommand = 
        "SELECT * FROM [orders] WHERE [EmployeeID] IN "
        + selectedItems + " ORDER BY [EmployeeID]";
    }
    else
    {
      SqlDataSource2.SelectParameters.Clear();
    }       
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>無題のページ</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" 
      runat="server"
      ConnectionString="<%$ ConnectionStrings:Northwind %>"
      SelectCommand="SELECT DISTINCT [EmployeeID] FROM [Orders] ORDER BY [EmployeeID]">
    </asp:SqlDataSource>
    <asp:CheckBoxList ID="CheckBoxList1" 
      runat="server"
      DataSourceID="SqlDataSource1" 
      DataTextField="EmployeeID"
      DataValueField="EmployeeID">
    </asp:CheckBoxList>
   
    <asp:Button ID="Button1" 
      runat="server" 
      Text="Show Selected Data"
      onclick="Button1_Click" />
       
    <asp:SqlDataSource ID="SqlDataSource2" 
      runat="server"
      ConnectionString="<%$ ConnectionStrings:Northwind %>" >
    </asp:SqlDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      DataSourceID="SqlDataSource2">
    </asp:GridView>   
  </div>
  </form>
</body>
</html>

Tags: ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  February 2020  >>
MoTuWeThFrSaSu
272829303112
3456789
10111213141516
17181920212223
2425262728291
2345678

View posts in large calendar