未処理の例外を処理する (C#)
サンプル コードを表示またはダウンロードします (ダウンロード方法)。
運用環境の 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 サイトのルートに新しい項目を追加します。
図 1: Web アプリケーションに Global.asax
を追加する
(クリックするとフルサイズの画像が表示されます)
Visual Studio によって作成される Global.asax
ファイルの内容と構造は、Web アプリケーション プロジェクト (WAP) と Web サイト プロジェクト (WSP) のどちらを使用しているかによって若干異なります。 WAP では、Global.asax
は Global.asax
と Global.asax.cs
という 2 つの個別のファイルとして実装されます。 Global.asax
ファイルには、.cs
ファイルを参照する @Application
ディレクティブのみが含まれ、対象のイベント ハンドラーは Global.asax.cs
ファイルで定義されます。 WSP の場合、Global.asax
という 1 つのファイルのみが作成され、イベント ハンドラーは <script runat="server">
ブロックで定義されます。
Visual Studio のグローバル アプリケーション クラス テンプレートによって WAP に作成された Global.asax
ファイルには、Application_BeginRequest
、Application_AuthenticateRequest
、および Application_Error
という名前のイベント ハンドラーが含まれています。これらは、それぞれ HttpApplication
、BeginRequest
、AuthenticateRequest
、および Error
イベントのイベント ハンドラーです。 また、Application_Start
、Session_Start
、Application_End
、Session_End
という名前のイベント ハンドラーもあります。これらは、それぞれ、Web アプリケーションの起動時、新しいセッションの開始時、アプリケーションの終了時、セッションの終了時に起動されるイベント ハンドラーです。 Visual Studio によって WSP に作成された Global.asax
ファイルには、Application_Error
、Application_Start
、Session_Start
、Application_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
イベントをトリガーした例外に関する情報を取得し、HttpException
を lastErrorWrapper
という変数に保存します。 次に、発生元の例外の種類、メッセージ、スタック トレースを 3 つの文字列変数に保存し、lastErrorWrapper
が Error
イベントをトリガーした実際の例外であるかどうか (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
クラスはメール メッセージを表し、To
、From
、Subject
、Body
、Attachments
などのプロパティを持ちます。 SmtpClass
は、指定された SMTP サーバーを使用して MailMessage
オブジェクトを送信するために使用されます。SMTP サーバー設定は、Web.config file
ファイルの <system.net>
要素でプログラムまたは宣言によって指定できます。 ASP.NET アプリケーションで電子メール メッセージを送信する方法の詳細については、「ASP.NET Web ページ サイトからの電子メールの送信」および「System.Net.Mailを参照してください。
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 を参照)。
図 2: ハンドルされない例外が発生するたびに、開発者にメール通知が送信される
(クリックするとフルサイズの画像が表示されます)
図 3: メール通知には、例外の詳細の YSOD が添付ファイルとして含まれている
(クリックするとフルサイズの画像が表示されます)
カスタム エラー ページの使用について
このチュートリアルでは、ハンドルされない例外が発生したときに Global.asax
と Application_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 を参照)。
図 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 つのライブラリについて説明します。
プログラミングに満足!
もっと読む
この記事で説明したトピックの詳細については、次のリソースを参照してください。