DataPagerの挙動について

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 632

tryCSharp

score 26

前提・実現したいこと

いつもお世話になっております。

DataPager付のListViewを持つWebForm1とLabelを持つWebForm2があります。
WebForm1のListViewはボタンを持ち、PostBackUrlにはWebForm2を設定しています。

WebForm1のListViewのボタンを押し、WebForm2へ遷移後、ブラウザ(Firefox)の戻るボタンを押し、WebForm1へ戻ります。
その後、WebForm1のDataPagerで任意のページを選択するとWebForm2を参照してしまいエラーが発生します。

なお、Internet ExplolerとChromeでは発生しません。また、ローカルでデバッグ時にも発生しません。

発生している問題・エラーメッセージ

'/FindControlTest' アプリケーションでサーバー エラーが発生しました。
ランタイム エラー
説明: サーバーでアプリケーション エラーが発生しました。このアプリケーションの現在のカスタム エラー設定では、セキュリティ上の理由により、アプリケーション エラーの詳細をリモート表示できません。ただし、ローカル サーバー コンピューターで実行されているブラウザーで表示することはできます。

詳細: このエラー メッセージの詳細をリモート コンピューターで表示できるようにするには、現在の Web アプリケーションのルート ディレクトリにある "web.config" 構成ファイル内に、<customErrors> タグを作成してください。その後で、この <customErrors> タグで "mode" 属性を "off" に設定してください。


<!-- Web.Config 構成ファイル -->

<configuration>
    <system.web>
        <customErrors mode="Off"/>
    </system.web>
</configuration>


メモ: 現在表示されているエラー ページをカスタム エラー ページに変更するには、アプリケーションの <customErrors> 構成タグの "defaultRedirect" 属性をカスタム エラー ページ URL に置き換えます。


<!-- Web.Config 構成ファイル -->

<configuration>
    <system.web>
        <customErrors mode="RemoteOnly" defaultRedirect="mycustompage.htm"/>
    </system.web>
</configuration>

該当のソースコード

WebForm1

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="FindControlTest.WebForm1" %>

<!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:ListView ID="ListView1" runat="server" DataKeyNames="ID" 
            DataSourceID="SqlDataSource1">
            <AlternatingItemTemplate>
                <tr style="">
                    <td>
                        <asp:Button ID="Button1" runat="server" Text="ジャンプ" CommandName="Select" PostBackUrl="WebForm2.aspx" />
                        <asp:Label ID="IDLabel" runat="server" Text='<%# Eval("ID") %>' />
                    </td>
                </tr>
            </AlternatingItemTemplate>
            <EditItemTemplate>
                <tr style="">
                    <td>
                        <asp:Button ID="UpdateButton" runat="server" CommandName="Update" Text="更新" />
                        <asp:Button ID="CancelButton" runat="server" CommandName="Cancel" 
                            Text="キャンセル" />
                    </td>
                    <td>
                        <asp:Label ID="IDLabel1" runat="server" Text='<%# Eval("ID") %>' />
                    </td>
                </tr>
            </EditItemTemplate>
            <EmptyDataTemplate>
                <table runat="server" style="">
                    <tr>
                        <td>
                            データは返されませんでした。</td>
                    </tr>
                </table>
            </EmptyDataTemplate>
            <InsertItemTemplate>
                <tr style="">
                    <td>
                        <asp:Button ID="InsertButton" runat="server" CommandName="Insert" Text="挿入" />
                        <asp:Button ID="CancelButton" runat="server" CommandName="Cancel" Text="クリア" />
                    </td>
                    <td>
                        <asp:TextBox ID="IDTextBox" runat="server" Text='<%# Bind("ID") %>' />
                    </td>
                </tr>
            </InsertItemTemplate>
            <ItemTemplate>
                <tr style="">
                    <td>
                        <asp:Button ID="Button1" runat="server" Text="ジャンプ" CommandName="Select" PostBackUrl="WebForm2.aspx" />
                        <asp:Label ID="IDLabel" runat="server" Text='<%# Eval("ID") %>' />
                    </td>
                </tr>
            </ItemTemplate>
            <LayoutTemplate>
                <table runat="server">
                    <tr runat="server">
                        <td runat="server">
                            <table ID="itemPlaceholderContainer" runat="server" border="0" style="">
                                <tr runat="server" style="">
                                    <th runat="server">
                                        &nbsp;</th>
                                    <th runat="server">
                                        ID</th>
                                </tr>
                                <tr ID="itemPlaceholder" runat="server">
                                </tr>
                            </table>
                        </td>
                    </tr>
                    <tr runat="server">
                        <td runat="server" style="">
                            <asp:DataPager ID="DataPager1" runat="server">
                                <Fields>
                                    <asp:NextPreviousPagerField ButtonType="Button" ShowFirstPageButton="True" 
                                        ShowNextPageButton="False" ShowPreviousPageButton="False" />
                                    <asp:NumericPagerField />
                                    <asp:NextPreviousPagerField ButtonType="Button" ShowLastPageButton="True" 
                                        ShowNextPageButton="False" ShowPreviousPageButton="False" />
                                </Fields>
                            </asp:DataPager>
                        </td>
                    </tr>
                </table>
            </LayoutTemplate>
            <SelectedItemTemplate>
                <tr style="">
                    <td>
                        <asp:Button ID="Button1" runat="server" Text="ジャンプ" CommandName="Select" PostBackUrl="WebForm2.aspx" />
                        <asp:Label ID="IDLabel" runat="server" Text='<%# Eval("ID") %>' />
                    </td>
                </tr>
            </SelectedItemTemplate>
        </asp:ListView>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
            ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
            ProviderName="<%$ ConnectionStrings:ConnectionString.ProviderName %>" 
            SelectCommand="SELECT &quot;ID&quot; FROM &quot;TABLE1&quot;">
        </asp:SqlDataSource>
    </div>
    </form>
</body>
</html>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace FindControlTest
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }
    }
}

WebForm2

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="FindControlTest.WebForm2" %>

<!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:Label ID="Label1" runat="server" Text="Label"></asp:Label>   
    </div>
    </form>
</body>
</html>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace FindControlTest
{
    public partial class WebForm2 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                if (Page.PreviousPage != null)
                {
                    ListView listview = (ListView)Page.PreviousPage.FindControl("ListView1");
                    Label1.Text = listview.SelectedDataKey[0].ToString();
                }
            }
        }
    }
}

試したこと

Internet Exploler、Chromeでは発生しませんでした。

補足情報(FW/ツールのバージョンなど)

Oracle: Oracle 11g
サーバーOS:Windows Server 2012
クライアントOS:Windows7
.NET:4.0
サーバーIIS:8.0
クライアントIIS:10.0 Express
Visual Studio:2015 Express for Web

追記(Web.config編集後のエラー)

'/FindControlTest' アプリケーションでサーバー エラーが発生しました。
オブジェクト参照がオブジェクト インスタンスに設定されていません。
説明: 現在の Web 要求を実行中に、ハンドルされていない例外が発生しました。エラーに関する詳細および例外の発生場所については、スタック トレースを参照してください。

例外の詳細: System.NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。

ソース エラー:

現在の Web 要求の実行中にハンドルされていない例外が生成されました。障害の原因および発生場所に関する情報については、下の例外スタック トレースを使って確認できます。

スタック トレース:


[NullReferenceException: オブジェクト参照がオブジェクト インスタンスに設定されていません。]
   FindControlTest.WebForm2.Page_Load(Object sender, EventArgs e) +165
   System.Web.UI.Control.LoadRecursive() +70
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +3177


バージョン情報: Microsoft .NET Framework バージョン:4.0.30319; ASP.NET バージョン:4.0.30319.17929 
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • tryCSharp

    2019/02/27 19:12

    コメントありがとうございます。
    1つ目のコメントに対する対応は明日させてください。
    でもPostBackUrlは使わないようにすべきとのことで他の方法を検討しようと思います。

    キャンセル

  • tryCSharp

    2019/02/28 07:42

    Web.config編集後のエラーを追記しました。

    キャンセル

  • SurferOnWww

    2019/02/28 08:58

    運用環境(すなわち IIS で実行)で、ブラウザが Firefox の場合に(IE, Chrome は OK)そういうサーバーエラーが出るのですよね・・・ 自分的には奇々怪々としか言いようがないです。自分の環境で調べてみます。少し時間をください。

    キャンセル

回答 2

checkベストアンサー

+2

自分の PC の Windows 10 Pro 64-bit の IIS10 で動く .NET 4.6.1 の ASP.NET Web Forms アプリで試してみましたが、自分の環境にある Firefox 65.0.1 で質問者さんと同じ問題が再現できました。IE11、Chrome 72.0.3626.119 では問題ありません。

原因は、Firefox の戻るボタンで WebForm2 から WebForm1 に戻った後、ページャーのボタンをクリックすると何故か WebForm2 へクロスページポストバックがかかってしまい、その場合は WebForm2 のコードビハインドの中にある listview.SelectedDataKey が null になるからでした。

WebForm1 のページャのボタンは、以下のように NumericPagerField が a 要素、NextPreviousPagerField が input type="submit" となっています。これは Firefox, IE, Chrome いずれも同じです。

<a href="javascript:__doPostBack('ListView1$DataPager1$ctl01$ctl02','')">3</a>
<input type="submit" name="ListView1$DataPager1$ctl02$ctl00" value="最後">


なので、クリックすれば普通のブラウザの動きとしては WebForm1 にポストバックするはずです。

しかしながら、Firefox の戻るボタンで WebForm2 から WebForm1 に戻った後は、ページャークリックで何故か WebForm2 にクロスページポストバックがかかってしまいます。(IE, Chrome は期待通り WebForm1 にポストバックがかかるので問題なしです)

そのクロスページポストバックの時は WebForm2 のコードビハインドの中の listview.SelectedDataKey が null になるので NullReferenceException がスローされます。

なぜ Firefox だけそういう訳の分からない奇々怪々なことになるのかは調べる気力がわいてこないので分かりません。

上の質問に対するコメントにも書きましたが、PostBackUrl(クロスページポストバック)はいろいろ問題が多く、個人的には絶対に使わないようにすべきと思っています。

解決策はクロスページポストバックの使用を避ける方向で検討すべきと思います。たとえば、WebForm1 のボタンクリックのイベントハンドラで WebForm2 へリダイレクトする。ID は Session またはクエリ文字列で渡すというように。

【追記】

evin101 さんの回答に対する 2019/03/01 10:34 の私のコメントで「調べた画像を私の回答欄に貼っておきます」と書きましたが、その画像を貼って若干の説明を追記しておきます。

:画像のページの名前は WebForm1 ⇒ 0065-PostBackUrlFormA、WebForm2 ⇒ 0065-PostBackUrlFormB に変えてますので注意してください。

ブラウザの「戻る」ボタンで目的のページをキャッシュから取得するのは、ASP.NET Web Forms アプリのデフォルトの設定では、IE も Chrome も Firefox も同じです。

下の画像は Firefox 65.0.1 と IE11 で、(1) 質問で言う WebForm1 を表示、(2)[ジャンプ]ボタンクリックで WebForm2 へクロスページポストバック、(3) ブラウザの[戻る]ボタンで WebForm1 に戻すという操作の要求・応答を Fiddler で見たものです。

イメージ説明

上の画像の #22 が Firefox での (1)、#27 が (2) です。画像の #59 が IE での (1)、#64 が (2) です。(3) の操作では Firefox も IE も要求は出ていません。画面は WebForm1 に戻るのでキャッシュから取得しています。

Firefox で (3) の操作の後にキャッシュから取得して表示された html ソースを見ると、以下の画像のように form 要素の action 属性が (2) でクロスページポストバックを行った先に(質問者さんの場合 WebForm2、私の場合は 0065-PostBackUrlFormB)に書き換えられてます。

イメージ説明

これが Firefox を使った場合に起こる問題の原因です。IE, Chrome の場合は書き換えられるようなことはありません。

なぜ Firefox でこんなことになるのかは調べてないので分かりません。WebFrom1 の初期画面からのポスト先が WebForm2 だったから書き換えてやろうという「小さな親切大きなお世話」ってやつかもしれませんね。

クロスページポストバックを使わないで、普通に WebForm1 のボタンクリックのイベントハンドラで WebForm2 へリダイレクトし、ID は Session またはクエリ文字列で渡すというようにした場合はどうなんでしょう? たぶん問題ないと思いますが、未確認です。

【追記2】

くどいようですが、原因についてさらに調べましたのでその結果を追記しておきます。興味がありましたら見ていただけると幸いです。

自分が調べた限りですが、要するに、IE, Chrome と Firefox ではキャッシュするタイミングに違いがあって、IE, Chrome は初期画面の、Firefox はユーザーが操作して JavaScript が動いた後の html ソースをキャッシュするという違いが今回の問題の原因になっているようです。

詳しくは以下の通りです。

クロスページポストバックを行うために Button に PostBackUrl="WebForm2.aspx" と設定すると、その Button からレンダリングされる input 要素は以下のようになります。type 属性が submit であることと onclick 属性に設定されたスクリプトに注意してください。

<input name="ListView1$ctrl0$Button1" 
  id="ListView1_Button1_0" 
  onclick='javascript:WebForm_DoPostBackWithOptions(
    new WebForm_PostBackOptions("ListView1$ctrl0$Button1", "", false, "", 
                                "WebForm2.aspx", false, false))' 
  type="submit" 
  value="ジャンプ">

onclick 属性に設定されている WebForm_DoPostBackWithOptions メソッドとその引数の WebForm_PostBackOptions は、HTTP ハンドラ WebResource.axd を使って埋め込みリソースから取得される js ファイルに含まれていて、以下の通りです。

function WebForm_PostBackOptions(eventTarget, eventArgument, validation, 
                    validationGroup, actionUrl, trackFocus, clientSubmit) {
    this.eventTarget = eventTarget;
    this.eventArgument = eventArgument;
    this.validation = validation;
    this.validationGroup = validationGroup;
    this.actionUrl = actionUrl;
    this.trackFocus = trackFocus;
    this.clientSubmit = clientSubmit;
}

function WebForm_DoPostBackWithOptions(options) {
    var validationResult = true;
    if (options.validation) {
        if (typeof(Page_ClientValidate) == 'function') {
            validationResult = Page_ClientValidate(options.validationGroup);
        }
    }
    if (validationResult) {
        if ((typeof(options.actionUrl) != "undefined") && 
            (options.actionUrl != null) && (options.actionUrl.length > 0)) {
            theForm.action = options.actionUrl;
        }
        if (options.trackFocus) {
            var lastFocus = theForm.elements["__LASTFOCUS"];
            if ((typeof(lastFocus) != "undefined") && (lastFocus != null)) {
                if (typeof(document.activeElement) == "undefined") {
                    lastFocus.value = options.eventTarget;
                }
                else {
                    var active = document.activeElement;
                    if ((typeof(active) != "undefined") && (active != null)) {
                        if ((typeof(active.id) != "undefined") && 
                            (active.id != null) && (active.id.length > 0)) {
                            lastFocus.value = active.id;
                        }
                        else if (typeof(active.name) != "undefined") {
                            lastFocus.value = active.name;
                        }
                    }
                }
            }
        }
    }
    if (options.clientSubmit) {
        __doPostBack(options.eventTarget, options.eventArgument);
    }
}


validation, trackFocus, clientSubmit は、上のボタンの onclick 属性に設定されたスクリプトで false に設定されるので、結局 theForm.action = options.actionUrl; だけの動作とななります。(即ち、form 要素の action 属性が WebForm2.aspx に書き換えられるだけ。theForm はインラインスクリプトとして定義されます)

ボタンは input type="submit" なので、その後 form が WebForm2.aspx に submit されます。

Firefox ではその時点での WebForm1.aspx の html ソースがキャッシュされるようで、それが、evin101 さんの言われる「JavaScript オブジェクト状態の保存」ということでしょうか・・・

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/28 12:16 編集

    ご回答ありがとうございます。
    検証までしていただき申し訳ございません。
    PostBackUrlは使わない方向で考えてみます。

    キャンセル

  • 2019/03/02 18:41

    色々調べていただいてありがとうございます。
    技術不足でお調べいただいた事になんのコメントも出来ませんが拝見させて頂いております。

    キャンセル

+1

すでにベストアンサーがついていますが、原因に心当たりがあったので検証してみました。

原因はおそらくbfcacheという機能が関わっています。
この機能は「戻る」「進む」ボタンを使用した場合に、メモリ内にキャッシュされたページを表示する機能です。
bfcache について覚えて帰ってもらいます。(転載) - oogatta のブログ

上記のブログによると、戻るボタンを使用した場合に、bfcacheにより以下のような挙動をします。

  • onload ハンドラのスキップ
  • JavaScript オブジェクト状態の保存

一方で、PostBackUrlを使用したクロスページポストバックを行った場合、以下のような動作をします。

  1. ボタンクリック等のクライアントイベント時、WebResouce.axdにより提供されるメソッドを呼び出す。
  2. 上記メソッドにより、form要素のaction属性をポストバック先のページに書き換える。
  3. 2.のページにPOSTする。

クロスページポストバックを行って「戻る」ボタンで戻った場合、上記2.の処理の結果がbfcacheの 「JavaScript オブジェクト状態の保存」によって残ったままになるため、通常のポストバックであるにもかかわらず別のページにポストバックしてしまうということになります。

回避策としてbfcacheを無効化やbfcacheの使用を検出して対策するといった方法が上記ブログにもありますが、私もSurferOnWwwさんの意見と同様でクロスページポストバックを避ける方向が良いのではないかと考えます。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/01 10:59

    >ブラウザの「戻る」ボタンで目的のページをキャッシュから取得するのは、ASP.NET Web Forms アプリのデフォルトの設定では、IE も Chrome も Firefox も同じです。

    https://developer.mozilla.org/ja/docs/Using_Firefox_1.5_caching
    上記のページにある「メモリ内にキャッシュ」という表現を使って他のブラウザとちょっと違うということを言いたかったのですが、ちょっと紛らわしかったので回答を修正しました。

    キャンセル

  • 2019/03/01 11:24

    回答の 1. 2. 3. の説明あたりもちょっと違うような気がします。私の回答欄に詳細を追記しますので、それを見ていただければと思います。

    キャンセル

  • 2019/03/02 11:25 編集

    上のコメントで、

    > 回答の 1. 2. 3. の説明あたりもちょっと違うような気がします。

    と書きましたが 1. ~ 3. はその通りでした。すみません。

    もう少し調べてみましたが、IE, Chrome, Firefox いずれも 1. ~ 3. の動きをします。問題は IE, Chrome は初期画面の状態でキャッシュするのに対し、Forefox は 2. の状態、即ち form 要素の action 属性が WebForm2 に書き換えられた状態でキャッシュするということのようです。

    要するに Firefox がキャッシュするタイミングが今回の問題の原因になっているのですが、それが、evin101 さんの言われる「JavaScript オブジェクト状態の保存」ということなのでしょうか・・・

    くどいようですが、自分の回答欄に調べた結果を追記しておきますので、興味がありましたら見てください。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.21%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる