WebSurfer's Home

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

Response.Redirect("url", false)

by WebSurfer 2010年9月17日 09:44

以下のようにした場合、Next.aspx に遷移するのは、Response.Redirect メソッドが実行された時点でしょうか、それとも、「時間のかかる処理」が終わった後でしょうか?

protected void Button1_Click(object sender, EventArgs e)
{
  Response.Redirect("Next.aspx", false);
  // ・・・・・・・・
  // 時間のかかる処理
  // ・・・・・・・・
}

MSDN フォーラムで上記の質問があって、その時は前者だと勘違いしていたんですが、正しくは後者(正確には、それよりさらに後のリダイレクト後)でした。

Response.Redirect は、ブラウザに対して HTTP Response Code 302 と遷移先 (Location) の url を返します。それを受け取ったブラウザが指定された url を GET しに行くという仕組みになっています。

(詳しくは、.NET エンタープライズ Web アプリケーション 開発技術大全の ポストバック処理 の図 10 を見てください。)

従って、Web サーバーからコード 302 と遷移先 url 情報を含んだレスポンスが帰ってこないと、ブラウザは Next.aspx に遷移できないということになります。

Response.Redirect メソッドの第2引数を false にすると、上記コードの「時間のかかる処理」が全部終わらないことにはサーバーからレスポンスは返ってきません。

という訳で、上記の質問の答えは後者(「時間のかかる処理」が終わった後)になります。

処理にかかる時間が長いと、その間フリーズしたようになってしまい、ユーザビリティの面でうまくないですね。でも、自分が調べた限りですが、まずレスポンスを返して、その後処理を継続して完了させる方法はなさそうです。AJAX を利用してプログレスをユーザーに知らせることで対応するぐらいしか手はなさそうです。

以下は余談ですが・・・

Response.Redirect("url") または Response.Redirect("url", true) とした場合は、処理をその時点で中断して、即、ブラウザに HTTP Response Code 302 と遷移先 (Location) の url を返します。ただし、中断することによって ThreadAbortException が発生します。

Response.End、Response.Redirect、または Server.Transfer メソッドを使用した場合のそれは仕様だそうです。詳しくは、Microsoft Support の 文書番号: 312629 を見てください。

その文書に回避策も書いてあって、Response.Redirect の場合は第 2 引数を false にすることだそうです。でも、自分は回避策は取っていません。例外がスローされても、どうせ終了するスレッドなので何も問題はないと勝手に判断しています。本当にそれでいいのかという一抹の不安はありますが・・・(汗)

----- 2013/3/21 追記 -----

最近知ったのですが、.NET 4 以降の MSDN ライブラリの説明で、End メソッドの使用は非推奨になっています。理由は、End メソッドでスローされる ThreadAbortException がパフォーマンスに悪影響を及ぼすからだそうです。

HttpResponse.Redirect メソッド (String) は End を呼び出します。従って、このメソッドの代わりに、HttpResponse.Redirect(String, Boolean) オーバーロードを使用し、その Boolean の引数に false を渡し、CompleteRequest() メソッドを呼び出すことが推奨されています。

詳細は、MSDN ライブラリの HttpResponse.Redirect メソッド (String, Boolean) を参照してください。

Tags:

ASP.NET

GridView に複数のテーブルを表示

by WebSurfer 2010年9月10日 13:10

あるテーブルを表示している GridView に 1 列追加し、そこに別テーブルからその行に関連する複数のレコード/フィールドを取得して表示するという話です。

結果は以下のような画面になります。MaterialDetails が追加した列です。

実行結果の画面

以下に具体的な例を説明します。

データベースで、「製品」は Products テーブルにて、「原料」は Materials テーブルにて管理されているとします。「製品」に使用されている「原料」の情報は Products や Materials テーブルには埋め込まず、関連を Relations という別テーブルで管理するとします。以下のような感じです。

製品テーブル (Products)
ProductID     int
ProductName   nvarchar(50)
UnitPrice     money

関連テーブル (Relations)
ProductID     int
MaterialID    int

原料テーブル (Materials)
MaterialID    int
MaterialName  nvarchar(50)
SupplierID    int

Products テーブルをベースに作った「製品」一覧の GridView に 1 列追加して、その列に、「製品」に使われている「原料」を表示します。

Materials テーブルから、関連するレコードの MaterialID, MaterialName, SupplierID をRelations テーブルを基に抽出し、追加した列に表示します。

製品 X には原料 a, b が、製品 Y には原料 a, e, f, h が使われているというように、追加した列に表示する Materials テーブルのレコード数は各行で一定ではありません。それに対応するための工夫が必要です。

具体的な手順は、以下のようになります。「原料」で表示するのが 1 フィールドだけでよいということであれば、もう少し簡単にできますが、基本的には以下の手順と同様です。

(1) Products の 型付 DataSet + TableAdapter

ソリューションにデータセット(xsd ファイル)を追加します。それにツールボックスから TableAdapter をドラッグ&ドロップします。

自動的に「TableAdapter 構成ウィザード」が起動しますので、そのウィザードを利用して Products テーブル用の型付 DataSet + TableAdapter を作成します。

基になる SELECT クエリは下記のようにします。

SELECT ProductID, ProductName, UnitPrice
FROM   Products

ウィザードが完了したら xsd ファイルを保存してください。その後、デザイナ画面のテーブル部分を右クリックして、[追加]→[列]で MaterialDetails 列を追加します。型はデフォルトで String になるはずです。

(2) Materials の型付 DataSet + TableAdapter

上記と同じ xsd ファイルの画面にツールボックスからもう一つ TableAdapter をドラッグ&ドロップし、クエリビルダで Materials テーブルと Relations テーブルの二つを追加します。

基になる SELECT クエリは下記のようにし、ウィザードを完了させます。

SELECT m.MaterialID, m.MaterialName, m.SupplierID
FROM   Materials AS m
       INNER JOIN Relations AS r
       ON m.MaterialID = r.MaterialID
WHERE  (r.ProductID = @ProductID)

上記 (1), (2) の結果は、以下の画面のようになります。

型付 DataSet + TableAdapter のデザイン画面

(3) GetProductsWithMaterials メソッド

ソリューションにクラスファイルを追加します。Products, Materials テーブルから必要なデータを抽出し、上記 (1) で作った型付 DataTable を初期化して返すメソッドを作成し、クラスファイルに実装します。

Products テーブルの TableAdapter を拡張する形で partial class にしてください。以下のような感じです。

using System;
using System.IO;
using System.ComponentModel;

namespace ProductsDataSetTableAdapters
{
  public partial class ProductsTableAdapter
  {
    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public ProductsDataSet.ProductsDataTable GetProductsWithMaterials()
    {
      ProductsDataSet.ProductsDataTable prodTable = this.GetData();
      MaterialsTableAdapter adapter = new MaterialsTableAdapter();
      foreach (ProductsDataSet.ProductsRow row in prodTable.Rows)
      {
        ProductsDataSet.MaterialsDataTable mtrlTable = 
          adapter.GetData(row.ProductID);
        StringWriter writer = new StringWriter();
        mtrlTable.WriteXml(writer);
        row.MaterialDetails = writer.ToString();
      }
      return prodTable;
    }
  }
}

(4) aspx ファイル

データを表示する Web ページ(aspx ファイル)を作成します。ObjectDataSource と GridView を利用した例は下記の通りです。MaterialDetails 列の xml データは GridView の RowDataBound イベントで適宜書き換えます。

この例では、xml データを DataTable に戻して、GridView の中にもう一つ GridView を作って表示しています。GridView を使わず、文字列で表示するなど、別の方法も検討してみてください。

<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>

<!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 GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      string xmlMaterialDetails = 
        (string)((DataRowView)e.Row.DataItem)["MaterialDetails"];
      StringReader reader = new StringReader(xmlMaterialDetails);
      ProductsDataSet.MaterialsDataTable table = 
        new ProductsDataSet.MaterialsDataTable();
      table.ReadXml(reader);
      GridView gv = new GridView();
      gv.DataSource = table;
      e.Row.Cells[3].Controls.Clear();
      e.Row.Cells[3].Controls.Add(gv);
      gv.DataBind();
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:ObjectDataSource ID="ObjectDataSource1" 
      runat="server" 
      OldValuesParameterFormatString="original_{0}" 
      SelectMethod="GetProductsWithMaterials" 
      TypeName="ProductsDataSetTableAdapters.ProductsTableAdapter">
    </asp:ObjectDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="ObjectDataSource1" 
      OnRowDataBound="GridView1_RowDataBound">
      <Columns>
        <asp:BoundField DataField="ProductID" 
          HeaderText="ProductID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="ProductName" 
          SortExpression="ProductName" />
        <asp:BoundField DataField="UnitPrice" 
          HeaderText="UnitPrice" 
          SortExpression="UnitPrice" />
        <asp:BoundField DataField="MaterialDetails" 
          HeaderText="MaterialDetails" 
          SortExpression="MaterialDetails" />
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

以上、ちょっと凝ったことをしましたが、「製品」:「原料」の関係が 1:1 もしくは 1:n (n=一定) の関係なら、3 つのテーブルを INNER JOIN した SELECT クエリをベースに、SqlDataSource と ListView で表示した方が簡単だと思います。

Tags: , ,

ASP.NET

Table の行のコピー作成

by WebSurfer 2010年9月9日 13:12

System.Web.UI.WebControls.Table の話です。

Table オブジェクトの、ある行のコピーを作って、その Table に追加するにはどうしたらいいでしょうか? 以下のような感じです。

オリジナルのテーブル 一行コピーして追加後のテーブル
オリジナル 一行コピー後

上のオリジナルの画像を作ったコードは以下の通りです。

<asp:Table id="Table1" 
    runat="server"
    CellPadding="10" 
    GridLines="Both"
    HorizontalAlign="Center">
    <asp:TableRow>
        <asp:TableCell>
            Item
        </asp:TableCell>
        <asp:TableCell>
            Description
        </asp:TableCell>
    </asp:TableRow>
    <asp:TableRow>
        <asp:TableCell>
            Row 0, Col 0
        </asp:TableCell>
        <asp:TableCell>
            Row 0, Col 1
        </asp:TableCell>
    </asp:TableRow>
    <asp:TableRow>
        <asp:TableCell>
            Row 1, Col 0
        </asp:TableCell>
        <asp:TableCell>
            Row 1, Col 1
        </asp:TableCell>
    </asp:TableRow>
</asp:Table>

この Table1 の一行目のコピーをプログラムで動的に作って、最終行の下に追加することを考えてみます。

以下のようにするとどうなるでしょう?

TableRow newRow = new TableRow();
newRow = Table1.Rows[0];
Table1.Rows.Add(newRow);

うまくいきそうに見えますが、これだと一行目が最下行の下に移動するだけです。

以下のように、TableRow だけでなく、その中身の TableCell のコピーも作って TableRow に設定し、それを Table に追加してやる必要があります。

TableRow newRow = new TableRow();
foreach (TableCell cell in Table1.Rows[0].Cells)
{
    TableCell newCell = new TableCell();
    newCell.Text = cell.Text;
    newRow.Cells.Add(newCell);
}
Table1.Rows.Add(newRow);

Table コンロトールはほとんど使わないし、実際にこのようなことをすることはなさそうなので、役に立たない話かもしれません。(汗) でも、昔、せっかく考えたことなので、忘れないように書いておきます。

Tags:

ASP.NET | ASP.NET

About this blog

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

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar