未処理の例外を処理する (C#)

作成者: Scott Mitchell

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

運用環境の Web アプリケーションでランタイム エラーが発生した場合は、開発者に通知し、エラーをログに記録して、後で診断できるようにすることが重要です。 このチュートリアルでは、ASP.NET がランタイム エラーを処理する方法の概要を示し、ハンドルされない例外が ASP.NET ランタイムに伝わるたびにカスタム コードを実行する 1 つの方法について説明します。

はじめに

ASP.NET アプリケーションでハンドルされない例外が発生すると、その例外は ASP.NET ランタイムに伝わり、Error イベントが発生して適切なエラー ページが表示されます。 エラー ページには、ランタイム エラーの死のイエロー スクリーン (YSOD)、例外の詳細の YSOD、カスタム エラー ページの 3 種類があります。 前のチュートリアルでは、リモート ユーザー用にカスタム エラー ページ、およびローカルにアクセスしているユーザー用に例外の詳細の YSOD を使用するようにアプリケーションを構成しました。

既定のランタイム エラーの YSOD よりも、サイトのイメージと一致する、人間にとってわかりやすいカスタム エラー ページを使用することをお勧めしますが、カスタム エラー ページを表示することは、包括的なエラー処理ソリューションの一部にすぎません。 運用環境のアプリケーションでエラーが発生した場合、例外の原因を特定して対処できるように、開発者にエラーが通知されることが重要です。 さらに、エラーの詳細を記録して、後でエラーを調べて診断できるようにする必要があります。

このチュートリアルでは、ログに記録して開発者に通知できるように、ハンドルされない例外の詳細にアクセスする方法について説明します。 このチュートリアルに続く 2 つのチュートリアルでは、少し設定するだけで開発者にランタイム エラーを自動的に通知し、その詳細をログに記録するエラー ログ ライブラリについて説明します。

Note

このチュートリアルで説明する情報は、ハンドルされない例外を独自の、またはカスタマイズされた方法で処理する必要がある場合に最も役立ちます。 例外をログに記録して開発者に通知するだけでよい場合は、エラー ログ ライブラリを使用するのが適切です。 次の 2 つのチュートリアルでは、このような 2 つのライブラリの概要を説明します。

Error イベントが発生したときにコードを実行する

イベントは、注目すべきことが発生したことを通知し、別のオブジェクトが応答してコードを実行するためのメカニズムをオブジェクトに提供します。 ASP.NET 開発者は、イベントの観点から考えることに慣れています。 訪問者が特定のボタンをクリックしたときにコードを実行する場合は、そのボタンの Click イベントのイベント ハンドラーを作成し、そこにコードを配置します。 ASP.NET ランタイムは、ハンドルされない例外が発生するたびに Error イベントを発生させるため、エラーの詳細をログに記録するコードはイベント ハンドラーに配置されることになります。 しかし、Error イベントのイベント ハンドラーはどのように作成するのでしょうか?

Error イベントは、要求の有効期間中に HTTP パイプラインの特定のステージで発生する HttpApplication クラスの多くのイベントのうちの 1 つです。 たとえば、HttpApplication クラスの BeginRequest イベントはすべての要求の開始時に発生し、AuthenticateRequest イベントはセキュリティ モジュールが要求元を識別したときに発生します。 これらの HttpApplication イベントにより、ページ開発者は要求の有効期間内のさまざまな時点でカスタム ロジックを実行する手段を得ることができます。

HttpApplication イベントのイベント ハンドラーは、Global.asax という名前の特別なファイルに配置できます。 このファイルを Web サイトに作成するには、Global.asax という名前のグローバル アプリケーション クラス テンプレートを使用して、Web サイトのルートに新しい項目を追加します。

Sceenshot that highlights the Global dot A S A X file.

図 1: Web アプリケーションに Global.asax を追加する
(クリックするとフルサイズの画像が表示されます)

Visual Studio によって作成される Global.asax ファイルの内容と構造は、Web アプリケーション プロジェクト (WAP) と Web サイト プロジェクト (WSP) のどちらを使用しているかによって若干異なります。 WAP では、Global.asaxGlobal.asaxGlobal.asax.cs という 2 つの個別のファイルとして実装されます。 Global.asax ファイルには、.cs ファイルを参照する @Application ディレクティブのみが含まれ、対象のイベント ハンドラーは Global.asax.cs ファイルで定義されます。 WSP の場合、Global.asax という 1 つのファイルのみが作成され、イベント ハンドラーは <script runat="server"> ブロックで定義されます。

Visual Studio のグローバル アプリケーション クラス テンプレートによって WAP に作成された Global.asax ファイルには、Application_BeginRequestApplication_AuthenticateRequest、および Application_Error という名前のイベント ハンドラーが含まれています。これらは、それぞれ HttpApplicationBeginRequestAuthenticateRequest、および Error イベントのイベント ハンドラーです。 また、Application_StartSession_StartApplication_EndSession_End という名前のイベント ハンドラーもあります。これらは、それぞれ、Web アプリケーションの起動時、新しいセッションの開始時、アプリケーションの終了時、セッションの終了時に起動されるイベント ハンドラーです。 Visual Studio によって WSP に作成された Global.asax ファイルには、Application_ErrorApplication_StartSession_StartApplication_End、および Session_End イベント ハンドラーのみが含まれています。

Note

ASP.NET アプリケーションを配置する際は、Global.asax ファイルを運用環境にコピーする必要があります。 このコードがプロジェクトのアセンブリにコンパイルされるため、WAP に作成された Global.asax.cs ファイルは運用環境にコピーする必要はありません。

Visual Studio のグローバル アプリケーション クラス テンプレートによって作成されたイベント ハンドラーはすべてを網羅しているわけではありません。 イベント ハンドラーに Application_EventName という名前を付けることで、任意の HttpApplication イベントのイベント ハンドラーを追加できます。 たとえば、Global.asax ファイルに次のコードを追加して、AuthorizeRequest イベントのイベント ハンドラーを作成できます。

protected void Application_AuthorizeRequest(object sender, EventArgs e)
{
    // Event handler code
}

同様に、グローバル アプリケーション クラス テンプレートによって作成された不要なイベント ハンドラーを削除することもできます。 このチュートリアルでは、Error イベントのイベント ハンドラーのみが必要です。Global.asax ファイルから他のイベント ハンドラーを削除してもかまいません。

Note

"HTTP モジュール" は、HttpApplication イベントのイベント ハンドラーを定義する別の方法を提供します。 HTTP モジュールは、Web アプリケーション プロジェクト内に直接配置したり、別のクラス ライブラリに分離したりできるクラス ファイルとして作成されます。 HTTP モジュールは、クラス ライブラリに分離できるため、HttpApplication イベント ハンドラーを作成するためのより柔軟で再利用可能なモデルを提供します。 Global.asax ファイルは、それが配置されている Web アプリケーションに固有のものであるのに対し、HTTP モジュールはアセンブリにコンパイルできるため、その時点で HTTP モジュールを Web サイトに追加するには、アセンブリを Bin フォルダーにドロップし、Web.config にモジュールを登録するだけで済みます。 このチュートリアルでは、HTTP モジュールの作成と使用については説明しませんが、次の 2 つのチュートリアルで使用される 2 つのエラー ログ ライブラリは HTTP モジュールとして実装されています。 HTTP モジュールの利点の詳細については、「HTTP モジュールとハンドラーを使用してプラグ可能な ASP.NET コンポーネントを作成する」を参照してください。

ハンドルされない例外に関する情報を取得する

この時点で、Application_Error イベント ハンドラーを含む Global.asax ファイルがあります。 このイベント ハンドラーが実行されたときに、開発者にエラーを通知し、その詳細をログに記録する必要があります。 これらのタスクを実行するには、まず発生した例外の詳細を特定する必要があります。 Server オブジェクトの GetLastError メソッドを使用して、Error イベントの発生原因となったハンドルされない例外の詳細を取得します。

protected void Application_Error(object sender, EventArgs e)
{
    // Get the error details
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;
}

GetLastError メソッドは、.NET Framework のすべての例外の基本データ型である Exception 型のオブジェクトを返します。 ただし、上記のコードでは、GetLastError によって返された Exception オブジェクトを HttpException オブジェクトにキャストしています。 ASP.NET リソースの処理中に例外がスローされたために Error イベントが発生した場合、スローされた例外は HttpException 内にラップされます。 Error イベントを発生させた実際の例外を取得するには、InnerException プロパティを使用します。 存在しないページの要求など、HTTP ベースの例外が原因で Error イベントが発生した場合は、HttpException がスローされますが、内部例外は含まれません。

次のコードでは、GetLastErrormessage メッセージを使用して、Error イベントをトリガーした例外に関する情報を取得し、HttpExceptionlastErrorWrapper という変数に保存します。 次に、発生元の例外の種類、メッセージ、スタック トレースを 3 つの文字列変数に保存し、lastErrorWrapperError イベントをトリガーした実際の例外であるかどうか (HTTP ベースの例外の場合)、または要求の処理中にスローされた例外のラッパーにすぎないかどうかを確認します。

protected void Application_Error(object sender, EventArgs e)
{
    // Get the error details
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;

    Exception lastError = lastErrorWrapper;
    if (lastErrorWrapper.InnerException != null)
        lastError = lastErrorWrapper.InnerException;

    string lastErrorTypeName = lastError.GetType().ToString();
    string lastErrorMessage = lastError.Message;
    string lastErrorStackTrace = lastError.StackTrace;
}

この時点で、例外の詳細をデータベース テーブルに記録するコードを記述するために必要なすべての情報が揃いました。 目的のエラーの詳細 (種類、メッセージ、スタック トレースなど) ごとに列を持つデータベース テーブルを作成し、要求されたページの URL や現在ログオンしているユーザーの名前などのその他の有用な情報も含めることができます。 Application_Error イベント ハンドラーでは、データベースに接続し、テーブルにレコードを挿入します。 同様に、開発者にメールでエラーを警告するコードを追加することもできます。

次の 2 つのチュートリアルで説明するエラー ログ ライブラリでは、このような機能がすぐに使用できるように提供されているため、このエラー ログと通知を自分で構築する必要はありません。 ただし、Error イベントが発生していること、および Application_Error イベント ハンドラーを使用してエラーの詳細をログに記録し、開発者に通知できることを示すために、エラーの発生時に開発者に通知するコードを追加してみましょう。

ハンドルされない例外の発生時に開発者に通知する

運用環境でハンドルされない例外が発生した場合は、開発チームに警告して、エラーを評価し、必要なアクションを決定できるようにすることが重要です。 たとえば、データベースへの接続中にエラーが発生した場合は、接続文字列を再確認し、場合によっては Web ホスティング会社のサポート チケットを開く必要があります。 プログラミング エラーが原因で例外が発生した場合は、将来的にこのようなエラーが発生しないように、追加のコードまたは検証ロジックを追加する必要がある場合があります。

System.Net.Mail 名前空間の .NET Framework クラスを使用すると、メールを簡単に送信できます。 MailMessage クラスはメール メッセージを表し、ToFromSubjectBodyAttachments などのプロパティを持ちます。 SmtpClass は、指定された SMTP サーバーを使用して MailMessage オブジェクトを送信するために使用されます。SMTP サーバー設定は、Web.config file ファイルの <system.net> 要素でプログラムまたは宣言によって指定できます。 ASP.NET アプリケーションでメール メッセージを送信する方法の詳細については、記事「ASP.NET Web ページ サイトから電子メールを送信する」および「System.Net.Mail に関する FAQ」を参照してください。

Note

<system.net> 要素には、メールの送信時に SmtpClient クラスによって使用される SMTP サーバー設定が含まれています。 Web ホスティング会社には、アプリケーションからメールを送信するために使用できる SMTP サーバーがある可能性があります。 Web アプリケーションで使用する SMTP サーバーの設定については、Web ホストのサポート セクションにお問い合わせください。

エラーの発生時に開発者にメールを送信するには、Application_Error イベント ハンドラーに次のコードを追加します。

void Application_Error(object sender, EventArgs e)
{
    // Get the error details
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;

    Exception lastError = lastErrorWrapper;
    if (lastErrorWrapper.InnerException != null)
        lastError = lastErrorWrapper.InnerException;

    string lastErrorTypeName = lastError.GetType().ToString();
    string lastErrorMessage = lastError.Message;
    string lastErrorStackTrace = lastError.StackTrace;

    const string ToAddress = "support@example.com";
    const string FromAddress = "support@example.com";
    const string Subject = "An Error Has Occurred!";
    
    // Create the MailMessage object
    MailMessage mm = new MailMessage(FromAddress, ToAddress);
    mm.Subject = Subject;
    mm.IsBodyHtml = true;
    mm.Priority = MailPriority.High;
    mm.Body = string.Format(@"
<html>
<body>
  <h1>An Error Has Occurred!</h1>
  <table cellpadding=""5"" cellspacing=""0"" border=""1"">
  <tr>
  <tdtext-align: right;font-weight: bold"">URL:</td>
  <td>{0}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">User:</td>
  <td>{1}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">Exception Type:</td>
  <td>{2}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">Message:</td>
  <td>{3}</td>
  </tr>
  <tr>
  <tdtext-align: right;font-weight: bold"">Stack Trace:</td>
  <td>{4}</td>
  </tr> 
  </table>
</body>
</html>",
        Request.RawUrl,
        User.Identity.Name,
        lastErrorTypeName,
        lastErrorMessage,
        lastErrorStackTrace.Replace(Environment.NewLine, "<br />"));

    // Attach the Yellow Screen of Death for this error   
    string YSODmarkup = lastErrorWrapper.GetHtmlErrorMessage();
    if (!string.IsNullOrEmpty(YSODmarkup))
    {
        Attachment YSOD = 
            Attachment.CreateAttachmentFromString(YSODmarkup, "YSOD.htm");
        mm.Attachments.Add(YSOD);
    }

    // Send the email
    SmtpClient smtp = new SmtpClient();
    smtp.Send(mm);
}

上記のコードはかなり長いですが、その大部分は開発者に送信するメールに表示される HTML を作成しています。 このコードは、GetLastError メソッド (lastErrorWrapper) によって返された HttpException を参照することから始まります。 要求によって発生した実際の例外は lastErrorWrapper.InnerException を介して取得され、変数 lastError に割り当てられます。 種類、メッセージ、スタック トレースの情報は lastError から取得され、3 つの文字列変数に保存されます。

次に、mm という名前の MailMessage オブジェクトが作成されます。 メールの本文は HTML 形式で、要求されたページの URL、現在ログオンしているユーザーの名前、例外に関する情報 (種類、メッセージ、スタック トレース) が表示されます。 HttpException クラスの優れた点の 1 つは、GetHtmlErrorMessage メソッドを呼び出すことによって、例外の詳細の死のイエロー スクリーン (YSOD) の作成に使用される HTML を生成できることです。 このメソッドをここで使用し、例外の詳細の YSOD マークアップを取得し、それを添付ファイルとしてメールに追加します。 注意: Error イベントをトリガーした例外が HTTP ベースの例外 (存在しないページの要求など) の場合、GetHtmlErrorMessage メソッドは null を返します。

最後のステップでは、MailMessage を送信します。 これを行うには、新しい SmtpClient メソッドを作成し、その Send メソッドを呼び出します。

Note

このコードを Web アプリケーションで使用する前に、ToAddress および FromAddress 定数の値を support@example.com から、エラー通知メールの送信先および送信元のメール アドレスに変更する必要があります。 また、Web.config<system.net> セクションで SMTP サーバー設定を指定する必要があります。 使用する SMTP サーバー設定を決定するには、Web ホスト プロバイダーにお問い合わせください。

このコードを導入すると、エラーが発生するたびに、エラーの概要と YSOD を含むメール メッセージが開発者に送信されます。 前のチュートリアルでは、Genre.aspx にアクセスし、クエリ文字列を通じて Genre.aspx?ID=foo などの無効な ID 値を渡すことで、ランタイム エラーを示しました。 Global.asax ファイルを配置したページにアクセスすると、前のチュートリアルと同じユーザー エクスペリエンスが得られます。開発環境では、例外の詳細の死のイエロー スクリーンが引き続き表示されますが、運用環境ではカスタム エラー ページが表示されます。 この既存の動作に加えて、開発者にメールが送信されます。

図 2 は、Genre.aspx?ID=foo にアクセスしたときに受信したメールを示しています。 メールの本文には例外情報が要約され、YSOD.htm 添付ファイルには例外の詳細の YSOD に示される内容が表示されます (図 3 を参照)。

Screenshot that shows the email sent to the developer.

図 2: ハンドルされない例外が発生するたびに、開発者にメール通知が送信される
(クリックするとフルサイズの画像が表示されます)

Screenshot that shows that the email notification includes the exception details Y S O D as an attachment.

図 3: メール通知には、例外の詳細の YSOD が添付ファイルとして含まれている
(クリックするとフルサイズの画像が表示されます)

カスタム エラー ページの使用について

このチュートリアルでは、ハンドルされない例外が発生したときに Global.asaxApplication_Error イベント ハンドラーを使用してコードを実行する方法を示しました。 具体的には、このイベント ハンドラーを使用して開発者にエラーを通知しましたが、これを拡張してエラーの詳細をデータベースに記録することもできます。 Application_Error イベント ハンドラーが存在しても、エンド ユーザーのエクスペリエンスには影響しません。 エラーの詳細の YSOD、ランタイム エラーの YSOD、カスタム エラー ページなど、構成済みのエラー ページは引き続き表示されます。

カスタム エラー ページを使用する場合、Global.asax ファイルと Application_Error イベントが必要かどうか疑問に思うのは当然です。 エラーが発生すると、ユーザーにはカスタム エラー ページが表示されますが、開発者に通知してエラーの詳細をログに記録するコードをカスタム エラー ページの分離コード クラスに配置することはできないのでしょうか? カスタム エラー ページの分離コード クラスにコードを追加することはできますが、前のチュートリアルで説明した手法を使用すると、Error イベントをトリガーした例外の詳細にアクセスすることはできません。 カスタム エラー ページから GetLastError メソッドを呼び出すと、Nothing が返されます。

この動作の理由は、カスタム エラー ページがリダイレクト経由でアクセスされるためです。 ハンドルされない例外が ASP.NET ランタイムに達すると、ASP.NET エンジンは Error イベント (Application_Error イベント ハンドラーを実行する) を発生させ、Response.Redirect(customErrorPageUrl) を発行してユーザーをカスタム エラー ページに "リダイレクト" します。 Response.Redirect メソッドは、HTTP 302 状態コードを含む応答をクライアントに送信し、ブラウザーに新しい URL (つまり、カスタム エラー ページ) を要求するよう指示します。 その後、ブラウザーはこの新しいページを自動的に要求します。 ブラウザーのアドレス バーがカスタム エラー ページの URL に変わるため、エラーが発生したページとは別にカスタム エラー ページが要求されたことを確認できます (図 4 を参照)。

Screenshot that shows that the browser gets redirected when an error occurs.

図 4: エラーが発生すると、ブラウザーはカスタム エラー ページの URL にリダイレクトされる
(クリックするとフルサイズの画像が表示されます)

その結果、サーバーが HTTP 302 リダイレクトで応答すると、ハンドルされない例外が発生した要求が終了します。 カスタム エラー ページへの後続の要求は、まったく新しい要求です。この時点で、ASP.NET エンジンはエラー情報を破棄しています。さらに、前の要求のハンドルされない例外をカスタム エラー ページへの新しい要求に関連付ける方法はありません。 このため、GetLastError はカスタム エラー ページから呼び出されると null を返します。

ただし、エラーの原因となった同じ要求中にカスタム エラー ページを実行することは可能です。 Server.Transfer(url) メソッドは、指定された URL に実行を転送し、同じ要求内で処理します。 Application_Error イベント ハンドラーのコードをカスタム エラー ページの分離コード クラスに移動し、Global.asax で次のコードに置き換えることができます。

protected void Application_Error(object sender, EventArgs e)
{
    // Transfer the user to the appropriate custom error page
    HttpException lastErrorWrapper = 
        Server.GetLastError() as HttpException;

    if (lastErrorWrapper.GetHttpCode() == 404)
    {
        Server.Transfer("~/ErrorPages/404.aspx");
    }
    else
    {
        Server.Transfer("~/ErrorPages/Oops.aspx");
    }
}

ハンドルされない例外が発生すると、Application_Error イベント ハンドラーは HTTP 状態コードに基づいて適切なカスタム エラー ページに制御を転送します。 制御が転送されたため、カスタム エラー ページは Server.GetLastError を介してハンドルされない例外の情報にアクセスし、開発者にエラーを通知してその詳細をログに記録できます。 Server.Transfer 呼び出しは、ASP.NET エンジンがユーザーをカスタム エラー ページにリダイレクトするのを停止します。 代わりに、カスタム エラー ページの内容が、エラーを生成したページへの応答として返されます。

まとめ

ASP.NET Web アプリケーションでハンドルされない例外が発生すると、ASP.NET ランタイムによって Error イベントが発生し、構成済みのエラー ページが表示されます。 Error イベントのイベント ハンドラーを作成することで、開発者にエラーを通知したり、詳細をログに記録したり、他の方法で処理したりできます。 Error などの HttpApplication イベントのイベント ハンドラーを Global.asax ファイルに、または HTTP モジュールから作成する方法は 2 つあります。 このチュートリアルでは、開発者にメール メッセージでエラーを通知する Error イベント ハンドラーを Global.asax ファイルに作成する方法を説明しました。

ハンドルされない例外を独自の、またはカスタマイズされた方法で処理する必要がある場合は、Error イベント ハンドラーを作成すると便利です。 ただし、例外をログに記録したり開発者に通知したりするために独自の Error イベント ハンドラーを作成することは、数分でセットアップできる無料で使いやすいエラー ログ ライブラリが既に存在するため、時間の使い方として最も効率的であるというわけではありません。 次の 2 つのチュートリアルでは、このような 2 つのライブラリについて説明します。

プログラミングに満足!

もっと読む

この記事で説明したトピックの詳細については、次のリソースを参照してください。