ASP.NET CoreとC#の例外処理ベストプラクティス!エラーハンドリングの基本と実装方法
生徒
「C#のASP.NETでWebアプリを作っているのですが、プログラムが途中でエラーを起こして止まってしまいます。画面に変な英語のエラーメッセージが出てしまうのを防ぐにはどうすればいいですか?」
先生
「それはプログラムの例外処理、いわゆるエラーハンドリングが適切にできていないからですね。Webアプリケーション開発において、予期しないエラーが発生したときの対策は非常に重要です。」
生徒
「例外処理やエラーハンドリングという言葉は難しそうですが、初心者でも実装できるベストプラクティスや具体的なやり方はありますか?」
先生
「はい、ありますよ!今回はC#やパソコンの操作に慣れていないプログラミング未経験の方でもしっかりと理解できるように、例外処理の基本からASP.NET Coreでの最適な設定方法までを分かりやすく解説しますね。」
1. 例外処理とは何か
プログラムを実行している最中に発生する、予期しない問題や特殊なエラーのことをプログラミングの世界では例外と呼びます。そして、その例外が発生したときにプログラムが完全にフリーズしたり、強制終了したりしないように、あらかじめ準備しておく対策のことを例外処理、またはエラーハンドリングと言います。
例えば、私たちが日常生活で自動販売機にお金を入れてジュースを買う場面を想像してみてください。もし自動販売機の中でジュースが詰まってしまったとき、自動販売機自体が完全に沈黙してしまったら困りますよね。代わりに、画面に「お手数ですが店員を呼んでください」と表示されたり、お金がそのまま戻ってきたりするはずです。このように、トラブルが起きたときでもシステム全体が壊れないように次の行動を指示する仕組みが、Webアプリ開発における例外処理の役割です。
C#のWebアプリ開発、特にASP.NETという仕組みを使う場合、この例外処理を正しく実装していないと、Webサイトにアクセスした一般の利用者に、開発者しか見ないような複雑な英語のプログラムコードやシステムの内側が丸見えになってしまいます。これは見た目が悪いだけでなく、セキュリティの面でも非常に危険な状態です。そのため、適切なエラー管理を行うことが、プログラミング初心者から脱却するための最初のステップになります。
2. try-catch文の基本的な使い方
C#で例外処理を行うための最も基本となる道具が、try-catch文と呼ばれる仕組みです。これは日本語に直すと「試す(try)」と「捕まえる(catch)」という意味になります。エラーが起きそうな処理をあらかじめ指定の枠の中に隔離しておき、もし実際にエラーが起きたらそのエラーを捕まえて別の安全な処理に誘導するという、川の流れを切り替える水門のような役割を果たします。
パソコンの操作に慣れていない方に向けて説明すると、これは「もし書類の作成に失敗したら、ゴミ箱に捨てるのではなく、上司に報告して書き直す」という指示書をあらかじめパソコンに与えておくようなものです。まずは、最もシンプルな計算処理を例にして、このtry-catch文がどのように動くのかをプログラムコードで確認してみましょう。
int number1 = 10;
int number2 = 0;
try
{
int result = number1 / number2;
Console.WriteLine("計算が成功しました。");
}
catch (DivideByZeroException ex)
{
Console.WriteLine("エラーが発生しました:ゼロで割り算することはできません。");
}
上記のプログラムでは、算数のルールで禁止されている「ゼロでの割り算」を行おうとしています。そのため、普通に実行するとプログラムが途中で壊れてしまいます。しかし、全体をtryのブロックで囲み、後ろにcatchを用意しておくことで、ゼロで割ろうとした瞬間にエラーが捕まり、画面には安全なメッセージが表示されます。実際の実行結果は以下のようになります。
エラーが発生しました:ゼロで割り算することはできません。
3. 様々な種類のエラーを個別に捕まえる方法
エラーには色々な種類があります。先ほどの「ゼロでの割り算」以外にも、例えば「存在しないファイルを読み込もうとした」「インターネットの接続が切れてデータベースからデータを取得できなかった」「文字を入力すべき場所に数字を入れてしまった」など、原因は様々です。これらを専門用語で例外クラスと呼びます。
優れたWebアプリを作るためには、どんなエラーが起きたのかによって、その後の対応を変える必要があります。お腹が痛いときと、頭が痛いときで、飲む薬を変えるのと同じですね。C#では、catchという枠を縦に並べることで、発生したエラーの種類に応じた個別の救護処置を書くことができます。次の具体例を見てみましょう。
try
{
string[] userList = { "田中", "鈴木", "佐藤" };
string selectedUser = userList[5];
Console.WriteLine("ユーザーを取得しました。");
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine("指定された番号は存在しません。範囲外のアクセスです。");
}
catch (Exception ex)
{
Console.WriteLine("原因不明の一般的なエラーが発生しました。");
}
このプログラムでは、3人しか登録されていない名簿の中から「5番目の人」を取り出そうとしています。当然、そんな人はいないのでエラーになります。このときは、範囲外を意味するIndexOutOfRangeExceptionという専用のキャッチ枠に処理が飛びます。最後に書いてあるExceptionは、すべてのエラーの親玉のような存在で、上の枠で捕まえきれなかったあらゆるエラーを最後にまとめて引き受ける万能の枠です。実行結果は以下の通りになります。
指定された番号は存在しません。範囲外のアクセスです。
4. 最後に必ず実行するfinallyブロックの仕組み
例外処理には、tryとcatchのほかにもう一つ、finallyという非常に重要な枠を組み合わせることができます。これは、エラーが起きても起きなくても、プログラムの処理が成功しようが失敗しようが、「最後に必ず絶対に実行する処理」を記述するための場所です。日常の例で言うなら、「料理が上手に作れても、焦げて失敗しても、使ったフライパンは最後に必ず洗って片付ける」という約束事のようなものです。
特にWebアプリやデスクトップアプリの開発では、パソコンのメモリという記憶領域を一時的に借りてファイルを読み込んだり、データベースという情報の保管庫の扉を開けてデータを出し入れしたりします。もしエラーが起きたからといって、その扉を開けっぱなしにしてプログラムを終了してしまうと、パソコンの動作がどんどん重くなってしまったり、他の人がそのファイルにアクセスできなくなったりする不具合が起きます。そのため、この後片付けの処理は必須となります。
System.IO.StreamReader fileReader = null;
try
{
fileReader = new System.IO.StreamReader("input.txt");
string textContent = fileReader.ReadToEnd();
Console.WriteLine("ファイルを正常に読み込みました。");
}
catch (System.IO.FileNotFoundException ex)
{
Console.WriteLine("指定されたファイルがパソコン内に見つかりませんでした。");
}
finally
{
if (fileReader != null)
{
fileReader.Close();
Console.WriteLine("ファイルの後片付け処理が完了しました。");
}
}
このプログラムでは、パソコン内にあるテキストファイルを読み込もうとしています。もし「input.txt」というファイルが存在しなければcatchに進みますが、その後に必ずfinallyの中身が動き、ファイルを開くために使っていた道具をきれいに片付けます。仮にファイルが無事に読めた場合でも、同様に片付けが行われます。実行結果は以下のようになり、安全性が保証されます。
指定されたファイルがパソコン内に見つかりませんでした。
ファイルの後片付け処理が完了しました。
5. ASP.NETにおける一括例外処理のベストプラクティス
ここまでは、C#の基本的な文法としての例外処理を説明してきましたが、ここからはWebアプリ開発の土台であるASP.NET Coreにおける本番向けの高度なテクニック、通称ベストプラクティスについて解説します。ベストプラクティスとは、「多くのプログラマーが経験を重ねてたどり着いた、最も効率的で不具合の少ないおすすめの書き方」という意味です。
Webアプリの中に、何百箇所もtry-catchを書くのは非常に大変ですし、書き忘れてしまう場所が出てくるかもしれません。そこでASP.NET Coreでは、アプリ全体の見張り番として、エラーを一箇所でまとめて捕まえるミドルウェアという仕組みを使うのが最も推奨されています。これをグローバル例外処理と呼びます。個別のページにエラー対策を書くのではなく、管制塔のような場所を一つ作り、そこで全ての不具合を監視するイメージです。
これを実現するために、ASP.NETの起動設定を行うファイルに、専用のエラーハンドリングを組み込みます。開発中のときは、プログラマーが原因を特定しやすいように詳しいエラー画面を出し、本番環境として一般のユーザーに公開するときは、親切な「お手数ですが時間をおいて再度お試しください」という案内画面に自動で切り替わるように設定するのが、プロの世界の鉄則です。具体的な設定の枠組みは、以下のように記述します。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/ErrorPage");
app.UseHsts();
}
app.Run();
このプログラムは、Webアプリが起動する瞬間に最初に読み込まれる設定書です。IsDevelopmentという命令を使って、現在が「開発中」か「本番中」かを自動で見分けています。これにより、初心者プログラマーでも安全かつ効率的にWebサイトの運営管理を行うことができるようになります。
6. 開発環境と本番環境のエラー画面の使い分け
前述の通り、インターネット上のシステム開発では、作業を行うパソコンの中だけの世界である「開発環境」と、実際に世界中の人々が見るインターネット上の世界である「本番環境」の2つのステージを明確に区別します。この切り替えを意識することが、例外処理を実装する上で非常に重要なベストプラクティスとなります。
もし、開発環境でエラーが出た場合は、何行目のどの文字が原因でプログラムが動かなくなったのかという詳細な内部情報が画面に出るべきです。これがないと、不具合の原因究明が難しくなり、開発作業が進まなくなってしまいます。一方で、本番環境で同じような画面を出してしまうと、悪いことを考えているハッカーと呼ばれる人たちに、「このWebサイトのシステムはこの部品を使っているから、ここを攻撃すれば壊せるぞ」というヒントを与えてしまうことになります。
そのため、本番環境では必ずあらかじめ作成しておいた、会社のロゴが入ったような綺麗で優しいエラー専用のウェブページを表示するように誘導します。このメリハリをつける設定を正しく行うことが、ASP.NET Coreで安全なエラーハンドリングを行うための大前提となります。初心者の方は、まずこの「環境による使い分け」という概念をしっかりと頭に入れておきましょう。
7. 例外をむやみに隠さないログ出力の大切さ
初心者がよくやってしまう間違ったエラー対策として、「エラーが発生したけれど、画面が止まると怖いから、中身を空っぽにしたcatchを使ってエラーを完全に無視して消し去ってしまう」というものがあります。これをプログラミングの世界では「例外を握りつぶす」と呼び、最もやってはいけない危険な行為とされています。なぜなら、画面上は動いているように見えても、裏側ではデータが保存されていなかったり、計算が狂ったまま進んでしまい、後から大問題に発展するからです。
エラーが発生したときは、それを隠すのではなく、開発者が後から気づけるように日記帳のような記録ファイルに不具合の履歴を残しておく必要があります。この記録のことをログと呼び、ログを書き出す作業をログ出力と言います。ASP.NET Coreには、標準の機能としてILoggerという優秀な記録係が最初から用意されています。この記録係に、どのようなエラーが何時何分に起きたのかを正確にメモさせる実装を行いましょう。
public class UserManager
{
private readonly ILogger<UserManager> _logger;
public UserManager(ILogger<UserManager> logger)
{
_logger = logger;
}
public void LoadUserData()
{
try
{
throw new System.IO.IOException("ディスクの読み込みエラーが発生しました。");
}
catch (System.IO.IOException ex)
{
_logger.LogError(ex, "ユーザーデータの読み込み中に重大なエラーが発生しました。");
}
}
}
上記のコード内にあるthrowという命令は、テスト用にわざと自分でエラーを発生させるための命令です。エラーが発生した瞬間に、_logger.LogErrorという命令が動き、パソコンの内部やサーバーの管理画面に、エラーの詳細な足跡がしっかりと刻まれます。これにより、利用者の画面にはエラーを隠しつつ、裏側でエンジニアが迅速に修理を行う体制を整えることができます。
8. 自作のエラーを作成して分かりやすく管理する方法
C#やASP.NET Coreのシステム開発に少しずつ慣れてくると、最初から用意されているエラーの種類だけでは、Webアプリ独自の細かいルールを表現しきれない場面が出てきます。例えば、「会員登録の画面で、パスワードが短すぎます」とか、「年齢の入力欄にマイナスの数字が入れられました」といった、Webアプリのルール違反によるエラーです。これらはパソコンの故障ではなく、利用者の操作ミスによる不具合です。
このような場合に、システム固有の分かりやすいエラーの枠組みを自分自身で新しく作ることができます。これをカスタム例外と呼びます。独自の例外を作っておくと、プログラムの他の場所でそのエラーを受け取ったときに、「あ、これはパスワードの文字数が足りないというエラーだな」と一目で判断できるようになり、画面に表示する案内文の切り替えが非常にスムーズになります。作り方はとても簡単で、以下のように記述します。
public class InvalidPasswordException : Exception
{
public InvalidPasswordException(string message) : base(message)
{
}
}
public class RegistrationService
{
public void CheckPassword(string password)
{
if (password.Length < 8)
{
throw new InvalidPasswordException("パスワードは8文字以上で設定してください。");
}
Console.WriteLine("適切なパスワードです。");
}
}
このように、あらかじめExceptionという土台をコピーして、自分専用のInvalidPasswordExceptionという名前のエラーの型を作っておきます。そして、文字数が8文字未満という条件に引っかかったときに、throwを使ってこの自作エラーを発射します。これをミドルウェアなどの一括監視所で捕まえることで、スマートで無駄のない、綺麗なプログラムを組み立てることができます。
9. 初心者が意識すべきエラーハンドリングの注意点
最後に、プログラミング未経験の方がC#でWebアプリ開発を進めるにあたって、絶対に意識してほしい例外処理の心構えをいくつかお伝えします。これらを守ることで、ベテランのエンジニアが書いたような信頼性の高い、安全で頑丈なWebアプリケーションを構築できるようになります。
まず第一に、「何でもかんでもtry-catchで囲まない」ということです。例外処理は非常に便利な仕組みですが、プログラムのすべての行を囲んでしまうと、どこで本当のバグが起きているのかが非常に分かりづらくなります。また、例外処理を動かすためには、パソコンの頭脳に少しだけ余計な負担がかかります。そのため、「通常の条件分岐であるif文で事前に防げるエラーは、if文で解決する」のが基本です。例えば、割り算の前に相手の数字がゼロでないかをif文で確認すれば、そもそもエラーは起きません。どうしても事前に防げない、ファイル操作や通信の不具合に対してのみ、例外処理を導入するようにしましょう。
第二に、「エラーメッセージには個人情報やパスワードを含めない」ことです。ログファイルにエラーの内容を出力するとき、そのメッセージの中に利用者のパスワードやクレジットカード番号がそのまま混ざって書き込まれてしまうと、そこから情報が漏洩する原因になります。エラーのログには、あくまでプログラムの動きに関する技術的な情報だけを載せるように注意深く設計してください。これらのベストプラクティスを意識しながら、何度もコードを書いて練習を積み重ねていきましょう。