WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

アプリケーションレベルの例外処理

by WebSurfer 3. October 2010 15:16

要求の処理中に Try/Catch ブロックで処置されなかった例外を、当該ページの Page_Error ハンドラおよび Global.asax の Application_Error ハンドラで処置し、例外を解釈してカスタムエラーページに適切なメッセージを表示するという話です。

以下の MSDN ライブラリに具体的な説明およびサンプルがあります。

【2016/12/12 訂正】.NET Framework 4 の記事のリンク先を英語版に変更しました。日本語版は、何かの手違いか、元の不完全なコード(Visual Studio 2008 の記事のコードと同じ)に戻ってしまったようです。

.NET Framework 4

How to: Handle Application-Level Errors

Complete Example for Error Handlers

Visual Studio 2008

方法 : アプリケーションレベルのエラーを処理する

エラー ハンドラーの完全なコード例

Visual Studio 2008 (.NET Framework 3.5) のサンプルコードには一部不備があるようで、自分が試した限りでは期待したとおりに動きませんでした。まず、その点を忘れないように書いておきます。

  1. Global.asax の Application_Error ですべての例外が処置されるので、web.config の customErrors 要素で defaultRedirect="HttpErrorPage.aspx" を設けても、それにリダイレクトされることはありません。なお、ここで「処置」とは Server.ClearError メソッドで前回の例外を削除することを意味します。
  2. Global.asax の Application_Error を修正して例外を処置しないようにすれば、HttpErrorPage.aspx にリダイレクトされるようになります。しかしながら、リダイレクト先のページでは Server.GetLastError で例外を取得することはできません(Transfer でないため)。
  3. web.config で customErrors mode="RemoteOnly" ... となっているため、開発環境でローカルのサーバにアクセスして試験する場合、カスタムエラーページは表示されません。(これは不具合というよりは、説明が不親切ということですが)

.NET Framework 4 のサンプルでは、上記の点は修正・改善されています。1, 2 については、例外が HttpException の場合 HttpErrorPage.aspx に Transfer することで解決しています。3 については、サンプルコードを mode="On" に変更しています。

.NET Framework 4 のサンプルの説明は上記の MSDN ライブラリのページにあります。それとダブルかもしれませんが、追加の説明を以下に書いておきます。

web.config: customErrors 要素の設定は以下のとおりです。mode="On" によって、ローカルでもカスタムエラーページが表示されるようにしています。

<customErrors mode="On" 
  defaultRedirect="DefaultRedirectErrorPage.aspx">
  <error statusCode="404" redirect="Http404ErrorPage.aspx"/>
</customErrors>

また、例外が処置されなかった場合に表示するカスタムエラーページの設定もされています。上の設定では、404 エラー (Not Found) の場合は Http404ErrorPage.aspx に、404 エラー以外の場合は DefaultRedirectErrorPage.aspx にリダイレクトされるはずです(「はず」と書いたのは、自分が試した限りでは期待通りに動かなかったからです。詳細は後述します)。

なお、error 要素で指定する特定の statusCode のエラーについては、defaultRedirect よりも、こちらが優先されるようです(MSDN ライブラリにはそのような記述は見つからず、自分手試した限りですが)。また、Transfer ではなく Redirect なので、遷移先のページでは Server.GetLastError メソッドによって発生した例外を取得できないことに注意してください。

Default.aspx: ボタンクリックで、例外をスロー (Button 1 ~ 3) したり、存在しないページへリダイレクト (Button 4 ~ 5) したり、アクセスが許可されてないファイルへリダイレクト (Button 6) したりします。

この中の Page_Error ハンドラで処置する例外(すなわちページレベルで処置する例外)は ArgumentOutOfRangeException(Button 2 で発生)のみです。InvalidOperationException(Button 1 で発生)の場合は GenericErrorPage.aspx に Transfer し、その他のすべての例外の場合は Global.asax に制御が移るようにコーディングされています(ただし、Button 6 が期待通りになりません。詳細は後述します)。

Global.asax: ページレベルで例外が処置されなかった場合、この中の Application_Error ハンドラに制御が飛んできます。Default.aspx からは、 Button 3 で Exception 例外が発生した場合のみ制御がここに飛んできます。(ちなみに、InvalidOperationException(Button 1 で発生)の場合は GenericErrorPage.aspx に Transfer されて処置され、ArgumentOutOfRangeException(Button 2 で発生)の場合はページレベルで処置されます)

Button 4 ~ 6 の場合は、存在しないページまたはアクセスが許可されてないファイルへのリダイレクトですから、HTTP 302 でブラウザが Location のページをサーバーに要求すると、サーバーではどのページも経由せず、直接 Global.asax の Application_Error ハンドラに制御が飛ぶはずです。(「はず」と書いたのは、Button 6 が期待通りにならないからです。詳細は後述します)

Application_Error ハンドラでは、例外の内、HttpException 以外をこの中で処置します。HttpException については、要求ページのファイル名に "NoCatch" が含まれる場合は何もせず return し、含まれない場合は HttpErrorPage.aspx に Transfer します。

GenericErrorPage.aspx: Default.aspx で InvalidOperationException がスローされた場合(Button 1 をクリックした場合)、このページに Transfer されてきて例外が処置されます。

HttpErrorPage.aspx: Global.asax で例外をチェックして、それが HttpException で、かつ、要求されたページのファイル名に "NoCatch" が含まれない場合はこのページに Transfer されてきて、HttpException が処置されます。(ファイル名に "NoCatch" が含まれる場合は、このページには Transfer されません・・・すなわち、例外は処置されません)

DefaultRedirectErrorPage.aspx: ページレベルでも Global.asax でも処置されなかった例外があった場合、このページにリダイレクトされます。ただし、web.config の customErrors 要素に error statusCode="404" の設定があるので、404 エラーの場合は Http404ErrorPage.aspx にリダイレクトされます。

Http404ErrorPage.aspx: ページレベルでも Global.asax でも処置されなかった例外があって、404 エラーの場合はこのページにリダイレクトされます。

Default.aspx を起動して、表示されるボタンをクリックして結果がどうなるか調べてみました。期待される結果は Default.aspx の表示に書いてあるとおりで(ソースのコメントは case 4 と 5 が間違っていますが)、開発サーバーを利用した場合は実際の結果も書いてあるとおりになりました。

しかしながら、IIS7 上で試してみたところ、Button 6 クリック(アクセスが許可されてないファイル Global-NoCatch.asax へリダイレクト)ではカスタムエラーページ (DefaultRedirectErrorPage.aspx) は表示されず、標準エラーページになってしまいます。標準エラーページのエラーメッセージは「HTTP エラー 404.7 - Not Found 要求フィルタ モジュールが、ファイル拡張子を拒否するように構成されています。」となっています。

ためしに、Global-NoCatch.asax を NonexistentPage.html(存在しない html ページ)に変更して試してみると、これもカスタムエラーページ (HttpErrorPage.aspx) は表示されず、標準エラーページ(HTTP エラー 404.0 - Not Found 探しているリソースは削除されたか、名前が変更されたか、または一時的に使用不可能になっています)になってしまいます。開発サーバーで試すと期待通り HttpErrorPage.aspx が表示されます。

IIS7 は統合パイプラインモードで検証しているので、html ファイルや .jpg ファイルなどの静的コンテンツファイルも ASP.NET にマップされており、カスタムエラーページが表示されるはずなのですが。

理由は、Global-NoCatch.asax や NonexistentPage.html を要求したときは、Global.asax の Application_Error ハンドラに制御が来る前に、標準エラーページが応答としてブラウザに返されてしまうからです。

何故、開発サーバーでは期待通り Global.asax の Application_Error ハンドラに制御が飛んでカスタムエラーページが返されるのに、IIS7 ではダメなのか、いろいろ調査しましたが不明です。

ググって調べているときに、「IIS 7. 0 でホストされている Web ページを参照しようとすると、エラー メッセージ:"HTTP エラーの 404.7-FILE_EXTENSION_DENIED"」という サポートオンライン のページを見つけたので、それに書いてあるように applicationHost.config の設定を変えて(fileExtension=".asax" allowed="false" を true にして)試してみましたが、ステータスコードが 404.0 に変わるだけで、やはり標準エラーページになってしまいます。

結局、今までのところ、Global-NoCatch.asax や NonexistentPage.html を要求されて、カスタムエラーページを返す方法は、TechNet のページ HTTP エラー <httpErrors>「カスタム エラー ページを追加する方法」以外に見つかりませんでした。この手順に従って、状態コード 404 で「このサイトで URL を実行」 の URL に Http404ErrorPage.aspx を指定したら、Global-NoCatch.asax や NonexistentPage.html の要求に対して、そのページが表示されるようになりました。

IIS7 の設定を変えて、開発サーバーで試したときと同じ結果にできないか、調査中です。ここまで調べただけで、もうイヤになってきているので、挫折する可能性が大ですが。(笑)

---------- 2010/10/12 追記 ----------

上に書きました、(1) Button 6 クリック(Global-NoCatch.asax へリダイレクト)および (2) 存在しない静的ファイルを要求した場合は、設定したカスタムエラーページが表示されない件につき理由を調べてみました。

ここに続けて書くと、ここの記事が長くなりすぎるので、customErrors と requestFiltering というタイトルで別に記事をポストしましたので、そちらを見てください。

Tags:

Exception Handling

SiteMap オブジェクト

by WebSurfer 28. September 2010 14:07

ASP.NET には、アプリケーションルートに配置した web.sitemap ファイル 1 箇所にサイトナビゲーションデータをまとめて格納しておき、SiteMapPath, TreeView, Menu コントロールを利用してサイト構造の表示や移動を行う機能が用意されています。

詳しい解説は MSDN ライブラリの「ASP.NET サイト ナビゲーションの概要」に述べられています。

このライブラリの最後の方に書いてある SiteMap オブジェクトの取得について、忘れないように書いておきます。

これは自分で XmlSiteMapProvider などを初期化して取得するのではなく、ASP.NET によって自動的に生成されるサイトのナビゲーション構造のインメモリ表現であり、単純に SiteMap として参照が取得できます。

例えば、あるページの Title タグに、web.sitemap の当該項目の title を表示する場合、以下のようにできます。SiteMap の初期化を自分で行う必要はありません。

protected void Page_Load(object sender, EventArgs e)  
{
    Page.Title = SiteMap.CurrentNode.Title;
}

それを知らなかったので、昔は以下のようにしていたのですが、そんな無駄なことをする必要は全くなかったようです。無知でした。(恥) MSDN ライブラリの解説からは読めなかったんです。

protected void Page_Load(object sender, EventArgs e)  
{  
  XmlSiteMapProvider provider = new XmlSiteMapProvider();  
  NameValueCollection attributes = new NameValueCollection(1);  
  attributes.Add("siteMapFile", "web.sitemap");  
  provider.Initialize("testProvider", attributes);  
  provider.BuildSiteMap();  
  SiteMapNode node = provider.CurrentNode;  
  Page.Title = node.Title;  
}  

Tags:

ASP.NET

XML ファイルの更新操作

by WebSurfer 27. September 2010 23:14

XML ファイルをデータソースに使った場合、表示するだけなら XmlDataSource を使えば、ほぼコーディングレスで Web アプリケーションを作成できます。ただし、XmlDataSource には更新機能がないので、必要な場合は自力でコードを書いて更新機能を実装する必要があります。

MSDN ライブラリの XmlDataSource の説明には、GetXmlDocument メソッドを使って XmlDataDocument オブジェクトを取得し、それに変更を加えてから Save するという方法が紹介されていますが、GridView や ListView 上で編集して更新するにはその方法は難しそうです。

それより、XML ファイル操作用のクラス(選択、削除、挿入、更新操作を行うメソッドを実装)を作り、そのクラスを ObjectDataSource を経由 GridView や ListView にバインドして操作するのが簡単そうです。

そのサンプルは MSDN ライブラリ の「GridView で XML ファイルをデータ ソースとして使いレコードを編集する方法」に紹介されています。そのサンプルに削除、挿入機能を加えて、さらに ID も更新できるように拡張したコードを書いておきます。なお、言語はサンプルの VB.NET を C# に変更しました。

まず、XML ファイル操作用クラス(UserInfoTable クラス)の UpdateDataSet メソッドを、ID も更新できるように変更します。具体的には、引数に original_id を追加し、original_id で DataSet の行を検索し、ヒットした行の当該項目を id に書き換えるよう修正します。次に、削除と挿入操作のためのメソッドを追加します。修正、追加後の UserInfoTable クラスは以下のようになります。

using System;
using System.Data;
using System.Web;
using System.ComponentModel;

public class XmlDataSet
{
  public class UserInfoTable : IDisposable
  {
    const string strXmlFile = "~/App_Data/UserInfo.xml";
    private DataSet myDataSet;

    public UserInfoTable()
    {
      myDataSet = new DataSet();
      myDataSet.Locale = 
        System.Globalization.CultureInfo.InvariantCulture;
      string filePath = 
        HttpContext.Current.Server.MapPath(strXmlFile);
      myDataSet.ReadXml(filePath);
    }

    public virtual void Dispose(bool disposing)
    {
      if (disposing)
      {
        myDataSet.Dispose();
      }
    }

    public void Dispose()
    {
      Dispose(true);
      System.GC.SuppressFinalize(this);
    }

    ~UserInfoTable()
    {
      Dispose(false);
    }

    [DataObjectMethod(DataObjectMethodType.Select, true)]
    public DataSet GetDataSet()
    {
      return myDataSet;
    }

    [DataObjectMethod(DataObjectMethodType.Update, true)]
    public void UpdateDataSet(string id, string name, string original_id)
    {
      string strFillter = "ID='" + original_id + "'";
      DataRow[] rows = myDataSet.Tables[0].Select(strFillter);
      if (rows.Length > 0)
      {
        rows[0]["ID"] = id;
        rows[0]["NAME"] = name;
        Save();
      }
    }

    [DataObjectMethod(DataObjectMethodType.Delete, true)]
    public void DeleteItem(string original_id)
    {
      string strFillter = "ID='" + original_id + "'";
      DataRow[] rows = myDataSet.Tables[0].Select(strFillter);
      if (rows.Length > 0)
      {
        rows[0].Delete();
        Save();
      }
    }

    [DataObjectMethod(DataObjectMethodType.Insert, true)]
    public void InsertItem(string id, string name)
    {
      DataRow row = myDataSet.Tables[0].NewRow();
      row["ID"] = id;
      row["NAME"] = name;
      myDataSet.Tables[0].Rows.Add(row);
      Save();
    }

    private void Save()
    {
      string filePath = 
        HttpContext.Current.Server.MapPath(strXmlFile);
      myDataSet.WriteXml(filePath, XmlWriteMode.IgnoreSchema);
    }
  } 
}

新しい aspx ファイルを作成し、それに ObjectDataSource と ListView を配置します。UserInfoTable クラスをベースに、ウィザードで ObjectDataSource と ListView を設定していくと、以下のようなコードになるはずです。

<asp:ObjectDataSource ID="ObjectDataSource1" 
  runat="server" 
  DeleteMethod="DeleteItem" 
  InsertMethod="InsertItem" 
  SelectMethod="GetDataSet" 
  TypeName="XmlDataSet+UserInfoTable" 
  UpdateMethod="UpdateDataSet">
  <DeleteParameters>
    <asp:Parameter Name="original_id" Type="String" />
  </DeleteParameters>
  <InsertParameters>
    <asp:Parameter Name="id" Type="String" />
    <asp:Parameter Name="name" Type="String" />
  </InsertParameters>
  <UpdateParameters>
    <asp:Parameter Name="id" Type="String" />
    <asp:Parameter Name="name" Type="String" />
    <asp:Parameter Name="original_id" Type="String" />
  </UpdateParameters>
</asp:ObjectDataSource>
<asp:ListView ID="ListView1" 
  runat="server" 
  DataSourceID="ObjectDataSource1" 
  EnableModelValidation="True">
</asp:ListView>

しかしなから、このままではうまく動きません。問題点は以下のとおりです

  • ListView に DataKeyNames="id" の定義がないので、id の値が ObjectDataSource に渡されません。
  • ObjectDataSource に OldValuesParameterFormatString="original_{0}" の設定がないので、id の新旧の区別ができません。
  • ListView の中身(Template やその中の TextBox, Button など)は自動生成されません。自力でコードを書く必要があります。
  • ListView に挿入の行を表示するため、InsertItemPosition="LastItem" を追加します。

以上の点を修正したコードは以下のとおりです。

<asp:ObjectDataSource ID="ObjectDataSource1" 
  runat="server"
  OldValuesParameterFormatString="original_{0}"
  DeleteMethod="DeleteItem" 
  InsertMethod="InsertItem" 
  SelectMethod="GetDataSet" 
  TypeName="XmlDataSet+UserInfoTable" 
  UpdateMethod="UpdateDataSet">
  <DeleteParameters>
    <asp:Parameter Name="original_id" Type="String" />
  </DeleteParameters>
  <InsertParameters>
    <asp:Parameter Name="id" Type="String" />
    <asp:Parameter Name="name" Type="String" />
  </InsertParameters>
  <UpdateParameters>
    <asp:Parameter Name="id" Type="String" />
    <asp:Parameter Name="name" Type="String" />
    <asp:Parameter Name="original_id" Type="String" />
  </UpdateParameters>
</asp:ObjectDataSource>
<asp:ListView ID="ListView1" 
  runat="server"
  DataKeyNames="id"
  DataSourceID="ObjectDataSource1"
  InsertItemPosition="LastItem"
  EnableModelValidation="True">
  <ItemTemplate>
    <tr>
      <td>
        <asp:Button ID="DeleteButton" 
          runat="server" 
          CommandName="Delete" 
          Text="削除" />
        <asp:Button ID="EditButton" 
          runat="server" 
          CommandName="Edit" 
          Text="編集" />
      </td>
      <td>
        <asp:Label ID="idLabel" 
          runat="server" 
          Text='<%# Bind("id") %>' />
      </td>
      <td>
        <asp:Label ID="nameLabel" 
          runat="server" 
          Text='<%# Bind("name") %>' />
      </td>
    </tr>
  </ItemTemplate>
  <InsertItemTemplate>
    <tr>
      <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>
      <td>
        <asp:TextBox ID="nameTextBox" 
          runat="server" 
          Text='<%# Bind("name") %>' />
      </td>
    </tr>
  </InsertItemTemplate>
  <LayoutTemplate>
    <table id="Table2" runat="server">
      <tr id="Tr1" runat="server">
        <td id="Td1" runat="server">
          <table ID="itemPlaceholderContainer" 
            runat="server">
            <tr id="Tr2" runat="server">
              <th id="Th1" runat="server">
              </th>
              <th id="Th2" runat="server">
                id</th>
              <th id="Th3" runat="server">
                name</th>
            </tr>
            <tr ID="itemPlaceholder" runat="server">
            </tr>
          </table>
        </td>
      </tr>
    </table>
  </LayoutTemplate>
  <EditItemTemplate>
    <tr>
      <td>
        <asp:Button ID="UpdateButton" 
          runat="server" 
          CommandName="Update" 
          Text="更新" />
        <asp:Button ID="CancelButton" 
          runat="server" 
          CommandName="Cancel" 
          Text="キャンセル" />
      </td>
      <td>
        <asp:TextBox ID="idTextBox" 
          runat="server" 
          Text='<%# Bind("id") %>' />
      </td>
      <td>
        <asp:TextBox ID="nameTextBox" 
          runat="server" 
          Text='<%# Bind("name") %>' />
      </td>
    </tr>
  </EditItemTemplate>
</asp:ListView>

Tags: , ,

ASP.NET

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  September 2020  >>
MoTuWeThFrSaSu
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar