Microsoft ASP.NET AJAXを使う4

現在の非同期ポストバックをキャンセルする

現在実行している非同期ポストバックをキャンセルするには、クライアントスクリプトでSys.WebForms.PageRequestManagerクラスのabortPostBackメソッドを呼び出します。abortPostBackメソッドは、現在実行中のポストバックをすべて停止します(「すべて」といっても同時に実行できる非同期ポストバックは1つだけですので、1つですが)。なお現在非同期ポストバックを実行中かどうかを判断するには、PageRequestManagerクラスのisInAsyncPostBackプロパティを使います。

前号の「UpdateProgressコントロールを使って、現在の状況を表示する」のサンプルを改造して、非同期ポストバックをキャンセルできるボタンをUpdateProgressコントロール内に表示する例を紹介します。

まず、非同期ポストバックをキャンセルするJavaScriptを記述します。次のようなCancelPostBack関数を呼び出せば、現在実行中の非同期ポストバックをキャンセルできます。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
//非同期ポストバックをキャンセルする
function CancelPostBack()
{
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    if (prm.get_isInAsyncPostBack())
    {
        //非同期ポストバック中のときは、キャンセルする
        prm.abortPostBack();
    }
}

isInAsyncPostBackプロパティで現在非同期ポストバックを実行中かどうかを調べ、もし実行中であれば、abortPostBackメソッドを呼び出して停止しています。

ここではUpdateProgressコントロール内にボタンを配置して、このボタンをクリックすれば非同期ポストバックを停止できるようにします。そのために、UpdateProgressコントロール内にHtmlButtonコントロールを配置して、onclick属性でCancelPostBack関数を呼び出します。

  1
  2
  3
  4
  5
  6
  7
<asp:UpdateProgress ID="UpdateProgress1" runat="server" DisplayAfter="0">
    <ProgressTemplate>
        読み込み中...<br />
        <input id="cancelbutton" type="button" value="中止"
            onclick="CancelPostBack()" />
    </ProgressTemplate>
</asp:UpdateProgress>

このようにして完成したコードの全体は、次のようになります。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
<%@ Page Language="VB" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<script runat="server">
 
    Protected Sub PackageSizeList_SelectedIndexChanged( _
            ByVal sender As Object, ByVal e As EventArgs)
        '3秒停止する
        System.Threading.Thread.Sleep(3000)
        
        Dim ddl As DropDownList = CType(sender, DropDownList)
        If ddl.SelectedValue <> "0" Then
            Dim fee As Integer = _
                Me.GetYoupackFee(Integer.Parse(ddl.SelectedValue))
            ResultLabel.Text = String.Format("料金は {0}円", fee)
        Else
            ResultLabel.Text = ""
        End If
    End Sub
 
    Public Function GetYoupackFee(ByVal packSize As Integer) As Integer
        If packSize <= 60 Then
            Return 600
        ElseIf packSize <= 80 Then
            Return 800
        ElseIf packSize <= 100 Then
            Return 1000
        ElseIf packSize <= 120 Then
            Return 1200
        ElseIf packSize <= 140 Then
            Return 1400
        ElseIf packSize <= 160 Then
            Return 1600
        ElseIf packSize <= 170 Then
            Return 1700
        Else
            Return -1
        End If
    End Function
    
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>ゆうパック送料検索</title>
</head>
<body>
    <h1>ゆうパック送料検索</h1>
    <p>同一都道府県内への配達、重量30kgまで</p>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
 
<script type="text/javascript">
//<![CDATA[
//非同期ポストバックをキャンセルする
function CancelPostBack()
{
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    if (prm.get_isInAsyncPostBack())
    {
        //非同期ポストバック中のときは、キャンセルする
        prm.abortPostBack();
    }
}
//]]>
</script>
 
        <asp:Label
            ID="Label1"
            runat="server"
            Text="荷物の大きさ(縦・横・高さの合計): "
            AssociatedControlID="PackageSizeList">
        </asp:Label>
        <asp:DropDownList
            ID="PackageSizeList"
            runat="server"
            AutoPostBack="True"
            OnSelectedIndexChanged="PackageSizeList_SelectedIndexChanged">
            <asp:ListItem Value="0">(選択してください)</asp:ListItem>
            <asp:ListItem Value="60">60cmまで</asp:ListItem>
            <asp:ListItem Value="80">80cmまで</asp:ListItem>
            <asp:ListItem Value="100">100cmまで</asp:ListItem>
            <asp:ListItem Value="120">120cmまで</asp:ListItem>
            <asp:ListItem Value="140">140cmまで</asp:ListItem>
            <asp:ListItem Value="160">160cmまで</asp:ListItem>
            <asp:ListItem Value="170">170cmまで</asp:ListItem>
        </asp:DropDownList>
        <br />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Label ID="ResultLabel" runat="server"></asp:Label>
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="PackageSizeList"
                    EventName="SelectedIndexChanged" />
            </Triggers>
        </asp:UpdatePanel>
        <asp:UpdateProgress ID="UpdateProgress1" runat="server" DisplayAfter="0">
            <ProgressTemplate>
                読み込み中...<br />
                <input id="cancelbutton" type="button" value="中止"
                    onclick="CancelPostBack()" />
            </ProgressTemplate>
        </asp:UpdateProgress>
    </div>
    </form>
</body>
</html>
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<%@ 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 PackageSizeList_SelectedIndexChanged(
        object sender, EventArgs e)
    {
        //3秒停止する
        System.Threading.Thread.Sleep(3000);
 
        DropDownList ddl = (DropDownList)sender;
        if (ddl.SelectedValue != "0")
        {
            int fee = this.GetYoupackFee(int.Parse(ddl.SelectedValue));
            ResultLabel.Text = string.Format("料金は {0}円", fee);
        }
        else
        {
            ResultLabel.Text = "";
        }
    }
 
    public int GetYoupackFee(int packSize)
    {
        if (packSize <= 60)
            return 600;
        else if (packSize <= 80)
            return 800;
        else if (packSize <= 100)
            return 1000;
        else if (packSize <= 120)
            return 1200;
        else if (packSize <= 140)
            return 1400;
        else if (packSize <= 160)
            return 1600;
        else if (packSize <= 170)
            return 1700;
        else
            return -1;
    }
 
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>ゆうパック送料検索</title>
</head>
<body>
    <h1>ゆうパック送料検索</h1>
    <p>同一都道府県内への配達、重量30kgまで</p>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        
<script type="text/javascript">
//<![CDATA[
//非同期ポストバックをキャンセルする
function CancelPostBack()
{
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    if (prm.get_isInAsyncPostBack())
    {
        //非同期ポストバック中のときは、キャンセルする
        prm.abortPostBack();
    }
}
//]]>
</script>
 
        <asp:Label
            ID="Label1"
            runat="server"
            Text="荷物の大きさ(縦・横・高さの合計): "
            AssociatedControlID="PackageSizeList">
        </asp:Label>
        <asp:DropDownList
            ID="PackageSizeList"
            runat="server"
            AutoPostBack="True"
            OnSelectedIndexChanged="PackageSizeList_SelectedIndexChanged">
            <asp:ListItem Value="0">(選択してください)</asp:ListItem>
            <asp:ListItem Value="60">60cmまで</asp:ListItem>
            <asp:ListItem Value="80">80cmまで</asp:ListItem>
            <asp:ListItem Value="100">100cmまで</asp:ListItem>
            <asp:ListItem Value="120">120cmまで</asp:ListItem>
            <asp:ListItem Value="140">140cmまで</asp:ListItem>
            <asp:ListItem Value="160">160cmまで</asp:ListItem>
            <asp:ListItem Value="170">170cmまで</asp:ListItem>
        </asp:DropDownList>
        <br />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Label ID="ResultLabel" runat="server"></asp:Label>
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="PackageSizeList"
                    EventName="SelectedIndexChanged" />
            </Triggers>
        </asp:UpdatePanel>
        <asp:UpdateProgress ID="UpdateProgress1" runat="server" DisplayAfter="0">
            <ProgressTemplate>
                読み込み中...<br />
                <input id="cancelbutton" type="button" value="中止"
                    onclick="CancelPostBack()" />
            </ProgressTemplate>
        </asp:UpdateProgress>
    </div>
    </form>
</body>
</html>

このコードを実行して、DropDownListで荷物の大きさを選択すると、UpdateProgressに「読み込み中...」という文字と、「中止」ボタンが表示されます。ここで「中止」ボタンをクリックすると、非同期ポストバックはキャンセルされます。

サーバー処理はキャンセルされない

勘違いしてはならないのは、abortPostBackメソッドはサーバー上での処理をキャンセルするものではないということです。ただ単にクライアントがサーバーからの受信をやめている(ソケットを閉じる)だけであり、サーバー側で行われている処理にはなんら影響を与えません。例えば、データベースの内容を変更する処理をabortPostBackメソッドでキャンセルしたとしても、変更は実行されてしまいます。

サーバー側の処理をキャンセルするには、キャンセルできるように独自に作成する必要があります。この方法に関しては、「ASP.NET AJAX を使用してサーバー タスクをキャンセルする」で説明されています。

補足:MSDNの「非同期ポストバックのキャンセル」で紹介されているサンプルでは、onclickを使わずに、キャンセルボタン(LinkButton)をUpdatePanelの中に入れています。この時キャンセルボタンが非同期ポストバックのトリガーとなるため、キャンセルボタンがクリックされるとinitializeRequestイベントが発生します。このinitializeRequestイベントハンドラでトリガーのIDを調べ、キャンセルボタンであればabortPostBackメソッドを呼び出すようにしています。

非同期ポストバックの要求をキャンセルする

次に、新たな非同期ポストバックの要求をキャンセルする方法を紹介します。これは、例えば、現在非同期ポストバックが実行中のときに、新たな非同期ポストバックをキャンセルしたいとき(2重送信を防止したいとき)などに役立ちます。

新たな非同期ポストバックの要求をキャンセルするには、initializeRequestイベントハンドラで取得できるSys.CancelEventArgsオブジェクトのcancelプロパティをtrueにします。

ここでもまた「UpdateProgressコントロールを使って、現在の状況を表示する」のサンプルを改造して、現在非同期ポストバックが実行中のときは新たな非同期ポストバックをキャンセルする例を示します。

JavaScriptは次のようになります。非常に簡単ですので、説明の必要も無いでしょう。initializeRequestイベントに関しては前号で説明しましたので、そちらをご覧ください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
//アプリケーションのloadイベントハンドラを追加
Sys.Application.add_load(ApplicationLoad)
 
//アプリケーションのloadイベントハンドラ
function ApplicationLoad(sender, args)
{
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    //イベントハンドラを追加
    prm.add_initializeRequest(InitializeRequest);
}
 
//非同期ポストバックの開始
function InitializeRequest(sender, args)
{
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    //非同期ポストバックが実行中のときは、新たなポストバックをキャンセルする
    if (prm.get_isInAsyncPostBack())
    {
        args.set_cancel(true);
    }
}

完成したコード全体は、次のようになります。<html>の部分以外は変わりありませんので、<html>内だけを示します。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>ゆうパック送料検索</title>
</head>
<body>
    <h1>ゆうパック送料検索</h1>
    <p>同一都道府県内への配達、重量30kgまで</p>
    <form id="form1" runat="server">
    <div>
        <asp:ScriptManager ID="ScriptManager1" runat="server">
        </asp:ScriptManager>
        
<script type="text/javascript">
//<![CDATA[
//アプリケーションのloadイベントハンドラを追加
Sys.Application.add_load(ApplicationLoad)
 
//アプリケーションのloadイベントハンドラ
function ApplicationLoad(sender, args)
{
    //PageRequestManagerを作成
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    //イベントハンドラを追加
    prm.add_initializeRequest(InitializeRequest);
}
 
//非同期ポストバックの開始
function InitializeRequest(sender, args)
{
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    //非同期ポストバックが実行中のときは、新たなポストバックをキャンセルする
    if (prm.get_isInAsyncPostBack())
    {
        args.set_cancel(true);
    }
}
//]]>
</script>
 
        <asp:Label
            ID="Label1"
            runat="server"
            Text="荷物の大きさ(縦・横・高さの合計): "
            AssociatedControlID="PackageSizeList">
        </asp:Label>
        <asp:DropDownList
            ID="PackageSizeList"
            runat="server"
            AutoPostBack="True"
            OnSelectedIndexChanged="PackageSizeList_SelectedIndexChanged">
            <asp:ListItem Value="0">(選択してください)</asp:ListItem>
            <asp:ListItem Value="60">60cmまで</asp:ListItem>
            <asp:ListItem Value="80">80cmまで</asp:ListItem>
            <asp:ListItem Value="100">100cmまで</asp:ListItem>
            <asp:ListItem Value="120">120cmまで</asp:ListItem>
            <asp:ListItem Value="140">140cmまで</asp:ListItem>
            <asp:ListItem Value="160">160cmまで</asp:ListItem>
            <asp:ListItem Value="170">170cmまで</asp:ListItem>
        </asp:DropDownList>
        <br />
        <asp:UpdatePanel ID="UpdatePanel1" runat="server">
            <ContentTemplate>
                <asp:Label ID="ResultLabel" runat="server"></asp:Label>
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="PackageSizeList"
                    EventName="SelectedIndexChanged" />
            </Triggers>
        </asp:UpdatePanel>
        <asp:UpdateProgress ID="UpdateProgress1" runat="server" DisplayAfter="0">
            <ProgressTemplate>
                読み込み中...
            </ProgressTemplate>
        </asp:UpdateProgress>
    </div>
    </form>
</body>
</html>

この例では新たな非同期ポストバックをキャンセルしてもそのことを全くユーザーに知らせていませんが、実際には分かるようにした方が良いでしょう。

2重送信を防止する

このような方法で二重送信を防止できますが、実際には、トリガー(この例ではDropDownList)を無効状態にすることにより、二重送信を防ぐ方が良いでしょう。

以下にトリガーを無効状態にすることにより二重送信を防止するようにした例を示します。違いはJavaScriptの部分だけですので、JavaScriptのコードのみを示します。initializeRequestイベントでトリガーを無効にして、endRequestイベントで再び有効に戻しています。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
//アプリケーションのloadイベントハンドラを追加
Sys.Application.add_load(ApplicationLoad)
 
//アプリケーションのloadイベントハンドラ
function ApplicationLoad(sender, args)
{
    //PageRequestManagerを作成
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    //イベントハンドラを追加
    prm.add_initializeRequest(InitializeRequest);
    prm.add_endRequest(EndRequest);
}
 
//非同期ポストバックの開始
function InitializeRequest(sender, args)
{
    var prm = Sys.WebForms.PageRequestManager.getInstance();
    //非同期ポストバックが実行中のときは、新たなポストバックをキャンセルする
    if (prm.get_isInAsyncPostBack())
    {
        args.set_cancel(true);
    }
    //DropDownListを無効にする
    $get('PackageSizeList').disabled = true;
}
 
//非同期ポストバックの完了
function EndRequest(sender, args)
{
    //DropDownListを有効にする
    $get('PackageSizeList').disabled = false;
}

非同期ポストバックに優先順位をつける

MSDNの「特定の非同期ポストバックの優先的処理」では上の「現在の非同期ポストバックをキャンセルする」と「非同期ポストバックの要求をキャンセルする」の方法を使用して、特定の非同期ポストバックを優先的に行う例が示されています。ここで紹介されている方法を簡単に説明すると、次のようなものです。

  • 以下の処理は、initializeRequestイベントハンドラで行う。
  • 前回非同期ポストバックを行ったトリガーのIDを記憶しておく。
  • 非同期ポストバックが現在実行中の時は、新たな非同期ポストバックが最優先とすべきものかをトリガーのIDを基にしらべる。もし新たな非同期ポストバックが最優先とすべきもので、前回の非同期ポストバックが最優先とすべきものでなければ、前回の非同期ポストバックをabortPostBackメソッドでキャンセルする。新たな非同期ポストバックも前回の非同期ポストバックも最優先とすべきものであれば、新たなポストバックをキャンセルする。
  • 非同期ポストバックが現在実行中で、新たな非同期ポストバックが最優先とすべきものでない場合は、前回のポストバックが最優先とすべきものか調べ、そうであれば新たな非同期ポストバックをキャンセルする。

つまり、非同期ポストバックの種類をトリガーのIDを使って調べること以外は、上で紹介した方法を使っているにすぎません。

なお、「Giving Precedence to a Specific Asynchronous Postback」のCommunity Contentでは、この方法がうまくいかないという指摘がされています。

コメント



ページ情報
[ トップ ]   [ 編集 | 凍結 | 差分 | バックアップ | 添付 | 複製 | 名前変更 | リロード ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]