by WebSurfer
2010年8月27日 17:36
マスターページに Button を配置して Click イベントで MenuPage にリダイレクトするようにしているとします。
あるページ(SourcePage とします)では、Button クリックで MenuPage ではなく別のページ(TargetPage とします)に遷移させたいので、SourcePage の Page_Load で Button.PostBackUrl に TargetPage を設定しました。
それで期待通りに、SourcePage で Button をクリックした場合は TargetPage に遷移していました。
ところが、マスターページの Page_Load に Page.PreviousPage を取得するコードを追加したところ、SourcePage で Button をクリックしても、TargetPage に遷移せず、MenuPage にリダイレクトされるようになってしまいました。
PostBackUrl の設定が無視されて Click イベントが発生しているようです。何故、Page.PreviousPage を取得すると、そうなってしまうのでしょうか?
サンプルのソースコードをアップしておきます。
MasterPage
<%@ Master Language="C#" ClassName="MasterExample" %>
<!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)
{
// Page.PreviousPage を取得すると Button1.Click イ
// ベントが発生する。
if (Page.PreviousPage != null)
{
Button btn2 =
(Button)Page.PreviousPage.Master.FindControl("Button1");
}
}
// Button1 の PostBackUrl, Text をコンテンツから設定
// するためのプロパティ。
public string SetPostBackUrl
{
set { Button1.PostBackUrl = value; }
}
public string SetButtonText
{
set { Button1.Text = value; }
}
// 通常は Button1 クリックで MenuPage へリダイレクト
protected void Button1_Click(object sender, EventArgs e)
{
Response.Redirect("MenuPage.aspx");
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<asp:ContentPlaceHolder id="head" runat="server">
</asp:ContentPlaceHolder>
</head>
<body>
<form id="form1" runat="server">
<asp:Button ID="Button1"
runat="server"
Text="Go To MenuPage"
OnClick="Button1_Click" />
<hr />
<div>
<asp:ContentPlaceHolder id="ContentPlaceHolder1"
runat="server">
</asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
SourcePage
<%@ Page Language="C#" MasterPageFile="~/MasterPage.master" %>
<script runat="server">
// このページの時は、Button1 クリックで、Menu ページでは
// なく TargetPage にクロスページポストバックで遷移する。
// そのため、以下のように Button1 の設定を変更する。
// SetPostBackUrl, SetButtonText プロパティはマスターペー
// ジで定義している。
protected void Page_Load(object sender, EventArgs e)
{
MasterExample m = (MasterExample)Page.Master;
m.SetPostBackUrl = "TargetPage.aspx";
m.SetButtonText = "Go To TargetPage";
}
</script>
<asp:Content ID="Content1"
ContentPlaceHolderID="head"
Runat="Server">
</asp:Content>
<asp:Content ID="Content2"
ContentPlaceHolderID="ContentPlaceHolder1"
Runat="Server">
<h1>This is the Source Page.</h1>
</asp:Content>
ASP.NET 内部でどのような処理がされているか見えないので多分に推測が入っていますが、以下のようなことではないかと思います。
-
SourcePage を要求すると、Page_Load で Button1.PostBackUrl に TargetPage が設定される。ブラウザ上で Button1 をクリックすると、クライアント側のスクリプトによって TargetPage が要求される。
-
その際、サーバーには、SourcePage の ViewState と共に、Button1 がクリックされたという情報も送られる。
-
サーバー側に制御が戻り、TargetPage がロードされ実行される。
-
そこで Page.PreviousPage が参照されていると、SourcePage の状態を取得するため、SourcePage がロードされ実行される。
-
その時、通常発生する Page.Init, Page.Load 等のイベントのみならず Button1.Click イベントも発生し(すべてのイベントを発生させて処理しないと正しく状態が取得できないはず)、そのハンドラで遷移先に設定してある MenuPage にリダイレクトされてしまう。
上記を裏付ける Microsoft のライブラリなどを探したのですが、残念ながら見つかるませんでした。見つけた中で一番信頼の置けそうなのが以下のサイトです。
Solve Postback Hassles with Cross-Page Postbacks in ASP.NET 2.0
というわけで、このような問題を避けるため、ASP.NET でクロスページポストバックを使うのはできるだけ避けた方がよさそうです。
今回のケースでは、対症療法的に以下のようにして対応できますが、予期しない副作用があるかもしれませんし。
protected void Button1_Click(object sender, EventArgs e)
{
if (!Page.IsCrossPagePostBack)
{
Response.Redirect("MenuPage.aspx");
}
}