Architect's Log

I'm a Cloud Architect. I'm highly motivated to reduce toils with driving DevOps.

Response.Endを呼び出すと必ずThreadAbortExceptionが発生する

こんなバグがありました。

protected new void Page_Load(object sender, EventArgs e) {
   try {
       /*
        * HttpResponse.TransmitFileでファイルの内容を応答に書き込む処理
        */
       Response.End();
   } catch (Exception){
       Response.Clear();
       Response.StatusCode = 500;
       Response.End();
   }
}

上記のコードは必ず500エラーになります。

Response.Endを呼び出すとThreadAbortExceptionが発生するからです。これは仕様です。

End、Redirect、および Transfer の呼び出しにより、現在の応答が途中で終了すると、ThreadAbortException 例外が発生します。

HttpResponse.End メソッド (System.Web)
現在バッファリングされているすべての出力をクライアントへ送信し、ページの実行を停止して、EndRequest イベントを発生させます。

修正後

以下のように実装する必要があります。

protected new void Page_Load(object sender, EventArgs e) {
   try {
       /*
        * HttpResponse.TransmitFileでファイルの内容を応答に書き込む処理
        */
       Response.End();
   } catch (ThreadAbortException) {
       // 何もしない。
   } catch (Exception){
       Response.Clear();
       Response.StatusCode = 500;
       Response.End();
   }
}

セッション状態値の存在チェックを拡張メソッドで実装する

セッション状態値の存在チェックを拡張メソッドで実装します。

ソースコード

using System;
using System.Web.SessionState;

namespace Extensions {
    public static class SystemWebSessionStateExtensions {
        /// <summary>
        /// 指定されたセッション状態値が存在するかどうかを示す値を取得します。
        /// </summary>
        /// <param name="self">HttpSessionStateのインスタンス。</param>
        /// <param name="name">セッション状態値のキー名。</param>
        /// <returns>セッション状態値が存在する場合はtrue。しない場合はfalse。</returns>
        public static bool Contains(this HttpSessionState self, string name) {
            return self[name] != null;
        }
    }
}

Repeaterコントロール内に配置したコントロールのイベントをハンドリングする

Repeaterコントロール内に配置したコントロールのイベントをハンドリングする際の注意点です。
ここでは例としてRepeaterコントロール内部にButtonを配置しています。

ソースコード

aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>
<!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>
        <div>
            <asp:Repeater ID="Repeater1" runat="server" EnableViewState="true">
                <ItemTemplate>
                    <div>
                        <%# ((WebApplication1.Item) Container.DataItem).ID %>
                        <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
                    <div>
                </ItemTemplate>
            </asp:Repeater>
        </div>
        <div>
            <asp:Label ID="Label1" runat="server" Text="" />
        </div>
    </div>
    </form>
</body>
</html>
aspx.cs
using System;
using System.Collections.Generic;

namespace WebApplication1 {
    public partial class _Default : System.Web.UI.Page {
        protected void Page_Load(object sender, EventArgs e) {
            if (IsPostBack == false) {
                Repeater1.DataSource = new List<Item>() {
                    new Item() { ID = "1" },
                    new Item() { ID = "2" }
                };
                Repeater1.DataBind();
            }
        }

        protected void Button1_Click(object sender, EventArgs e) {
            Label1.Text = "ボタンがクリックされました。";
        }
    }

    public class Item {
        public string ID { get; set; }
    }
}

動作

初期表示

ボタンクリック

注意点

ポストバックのときはDataBindしない

Repeaterを使用する場合に限りませんが、ポストバックのときもPage.Loadは発生します。
イベントの発生順序はPage.Load→Button.Clickの順なので、ポストバックのときにもDataBindしてしまうとButton.Clickが発生しません。

RepeaterのEnableViewStateをtrueにする

EnableViewStateをfalseにするとポストバックの際に送信されるViewStateにRepeater内部のコントロールが含まれないので、Button.Clickが発生しません。

ポストバック後もスクロール位置を保持する

どういうこと?

縦に長いページの場合、ポストバック後のスクロール位置の保持が有効です。

どうして?

ポストバック後に、再度スクロールを強いるのはユーザーフレンドリーではありません。

どうすれば?

Pageディレクティブで「MaintainScrollPositionOnPostback="true"」を宣言します。

<%@ Page MaintainScrollPositionOnPostback="true" %>

ASP.NET Ajax クライアント側フレームワークを読み込めませんでした

どういうこと?

ASP.NETでAjaxを使ったアプリを開発していたら、突然エラーが発生するようになった。

  • "ASP.NET Ajax クライアント側フレームワークを読み込めませんでした。"
  • "'Sys'は宣言されていません。"

どうして?

エラーメッセージから、Ajax Libraryが読み込めていないと推測される。原因は不明。

どうすれば?

ScriptManagerをコンテンツページからマスターページに移動したら、発生しなくなった。対症療法で気持ちが悪いがとりあえず解決。

2012/02/20追記

その後再び同一の現象が再現した。
Global.asaxのSession_Startに記述したルーティングの定義が原因だった。該当箇所をコメントアウトしたら解決した。

~(チルダ)で始まるパスを絶対パスに変換する

どういうこと?

ASP.NETではチルダでアプリケーションルートを表すことができます。これはあくまでもASP.NETの仕様なので、

<img src="~Hoge/Fuga/image.jpg" />

こんな風に書いても画像を参照できません。

どうすれば?

System.Web.VirtualPathUtility.ToAbsolute メソッドで、チルダで始まる仮想パスを絶対パスに変換すればOKです。
VirtualPathUtility.ToAbsolute メソッド (String) (System.Web)
仮想パスをアプリケーション絶対パスに変換します。