例外処理はプログラミングにおいて非常に重要です。ユーザーに親切なメッセージを表示するとともに、プログラムの安全性を確保するためです。ASP.NET Coreでは、デフォルトで多くのソリューションが提供されていますので、以下にまとめます。
1. Controllerを継承し、OnActionExecutedをオーバーライド
Visual Studioでコントローラーを新規作成する際、デフォルトでControllerクラスを継承します。OnActionExecutedをオーバーライドして例外処理を追加します。通常はBaseControllerを作成し、すべてのコントローラーに継承させます。コードは以下の通りです。
public class BaseController : Controller
{
public override void OnActionExecuted(ActionExecutedContext context)
{
var exception = context.Exception;
if (exception != null)
{
context.ExceptionHandled = true;
context.Result = new ContentResult
{
Content = $"BaseControllerエラー : { exception.Message }"
};
}
base.OnActionExecuted(context);
}
}
この処理方法の利点はもちろんシンプルなことですが、欠点はcshtmlページでエラーが発生した場合、完全にキャッチできないことです。もちろん、プロジェクトがWeb APIプロジェクトでビューがない場合は問題ありません。
2. ActionFilterAttributeを使用する
ActionFilterAttributeは属性で、IActionFilterとIResultFilterを実装しています。そのため、アクション内のエラーでもビュー内のエラーでも、理論的にはキャッチ可能です。新しいExceptionActionFilterAttributeを作成し、OnActionExecutedとOnResultExecutedをオーバーライドして例外処理を追加します。完全なコードは以下の通りです。
public class ExceptionActionFilterAttribute:ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
var exception = context.Exception;
if (exception != null)
{
context.ExceptionHandled = true;
context.Result = new ContentResult
{
Content = $"エラー : { exception.Message }"
};
}
base.OnActionExecuted(context);
}
public override void OnResultExecuted(ResultExecutedContext context)
{
var exception = context.Exception;
if (exception != null)
{
context.ExceptionHandled = true;
context.HttpContext.Response.WriteAsync($"エラー : {exception.Message}");
}
base.OnResultExecuted(context);
}
}
使用方法は2通りあります。
- コントローラーに
[TypeFilter(typeof(ExceptionActionFilter))]属性を付ける。 - Startupでフィルターとしてグローバルに注入する。
services.AddControllersWithViews(options =>
{
options.Filters.Add<ExceptionActionFilterAttribute>();
})
3. IExceptionFilterを使用する
ASP.NET Coreには5種類のフィルターが用意されていますが、IExceptionFilterはその1つで、名前の通り例外処理に使用されます。ASP.NET CoreではExceptionFilterAttributeが既にIExceptionFilterを実装しているため、ExceptionFilterAttributeを継承し、その中のメソッドをオーバーライドするだけです。同様にCustomExceptionFilterAttributeを作成し、ExceptionFilterAttributeを継承してOnExceptionをオーバーライドし、例外処理を追加します。完全なコードは以下の通りです。
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(ExceptionContext context)
{
context.ExceptionHandled = true;
context.HttpContext.Response.WriteAsync($"CustomExceptionFilterAttributeエラー:{context.Exception.Message}");
base.OnException(context);
}
}
注意点として、ExceptionFilterAttributeは非同期メソッドOnExceptionAsyncも提供しています。これら2つの処理メソッドは、どちらか1つだけオーバーライドすればよく、両方ともオーバーライドすると両方のメソッドが1回ずつ実行されます。
使用方法は2通りあります。
- コントローラーに
[CustomExceptionFilter]属性を付ける。 - Startupでフィルターとしてグローバルに注入する。
services.AddControllersWithViews(options =>
{
options.Filters.Add<CustomExceptionFilterAttribute>();
})
4. ExceptionHandlerを使用する
Startupでは、Visual Studioで新規作成したプロジェクトにデフォルトで以下が追加されます。
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
上記のコードは、開発環境ではapp.UseDeveloperExceptionPage()を使用し、本番環境ではhome/errorページにリダイレクトすることを意味します。
UseDeveloperExceptionPageは、具体的なエラー情報(Stack trace、Query、Cookies、Headersの4つ)を表示します。.NET Core 3.1以降では、リクエストヘッダーにaccept:text/htmlがある場合はHTMLページを返し、それ以外の場合はエラーメッセージのみを返すように変更されました。
注意:app.UseDeveloperExceptionPage()は最初に配置する必要があります。
if (!env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(builder =>
{
builder.Run(async context =>
{
var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
await context.Response.WriteAsync($"error:{exceptionHandlerPathFeature.Error.Message}");
});
});
}
5. カスタムMiddlewareで処理する
ミドルウェアを使用してグローバルに処理します。
public class ErrorHandlingMiddleware
{
private readonly RequestDelegate next;
public ErrorHandlingMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await next(context);
}
catch (System.Exception ex)
{
//例外処理
}
}
}