1. ホーム
  2. c#

[解決済み] ASP.NET Core Web APIの例外処理

2022-03-14 12:16:03

質問

長年、通常のASP.NET Web APIを使用してきた後、新しいREST APIプロジェクトにASP.NET Coreを使用しています。私はASP.NET Core Web APIで例外を処理する良い方法を見いだすことができません。私は例外処理フィルタ/属性を実装しようとしました。

public class ErrorHandlingFilter : ExceptionFilterAttribute
{
    public override void OnException(ExceptionContext context)
    {
        HandleExceptionAsync(context);
        context.ExceptionHandled = true;
    }

    private static void HandleExceptionAsync(ExceptionContext context)
    {
        var exception = context.Exception;

        if (exception is MyNotFoundException)
            SetExceptionResult(context, exception, HttpStatusCode.NotFound);
        else if (exception is MyUnauthorizedException)
            SetExceptionResult(context, exception, HttpStatusCode.Unauthorized);
        else if (exception is MyException)
            SetExceptionResult(context, exception, HttpStatusCode.BadRequest);
        else
            SetExceptionResult(context, exception, HttpStatusCode.InternalServerError);
    }

    private static void SetExceptionResult(
        ExceptionContext context, 
        Exception exception, 
        HttpStatusCode code)
    {
        context.Result = new JsonResult(new ApiResponse(exception))
        {
            StatusCode = (int)code
        };
    }
}

そして、これが私のStartupフィルター登録です。

services.AddMvc(options =>
{
    options.Filters.Add(new AuthorizationFilter());
    options.Filters.Add(new ErrorHandlingFilter());
});

私が抱えていた問題は、例外が発生したときに私の AuthorizationFilter で処理されない。 ErrorHandlingFilter . 昔のASP.NET Web APIで動作したように、そこで捕捉されると期待していたのですが。

では、アプリケーションの例外とアクション・フィルタの例外をすべてキャッチするにはどうすればよいのでしょうか。

解決方法は?

クイック&イージーな解決策。

ASP.NETルーティングの前にこのミドルウェアをミドルウェアの登録に追加するだけです。

app.UseExceptionHandler(c => c.Run(async context =>
{
    var exception = context.Features
        .Get<IExceptionHandlerPathFeature>()
        .Error;
    var response = new { error = exception.Message };
    await context.Response.WriteAsJsonAsync(response);
}));
app.UseMvc(); // or .UseRouting() or .UseEndpoints()


ロギングなどのためにDependency Injectionを有効にします。

ステップ1. スタートアップで、例外処理ルートを登録します。

// It should be one of your very first registrations
app.UseExceptionHandler("/error"); // Add this
app.UseEndpoints(endpoints => endpoints.MapControllers());

ステップ2. すべての例外を処理し、エラーレスポンスを生成するコントローラを作成します。

[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
public class ErrorsController : ControllerBase
{
    [Route("error")]
    public MyErrorResponse Error()
    {
        var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
        var exception = context.Error; // Your exception
        var code = 500; // Internal Server Error by default

        if      (exception is MyNotFoundException) code = 404; // Not Found
        else if (exception is MyUnauthException)   code = 401; // Unauthorized
        else if (exception is MyException)         code = 400; // Bad Request

        Response.StatusCode = code; // You can use HttpStatusCode enum instead

        return new MyErrorResponse(exception); // Your error model
    }
}

いくつかの重要な注意と観察。

  • 以下のことが可能です。 依存関係を注入する を Controller のコンストラクタに追加します。
  • [ApiExplorerSettings(IgnoreApi = true)] が必要です。さもないと、スワッシュバックルの威厳が壊れるかもしれません
  • もう一度 app.UseExceptionHandler("/error"); は、Startupの中で非常に上位の登録でなければなりません。 Configure(...) メソッドを使用します。メソッドの一番上に配置するのが無難でしょう。
  • のパスは app.UseExceptionHandler("/error") とコントローラ内の [Route("error")] は、例外ハンドラミドルウェアからリダイレクトされた例外をコントローラが処理できるようにするために、同じであるべきです。

以下は リンク をクリックすると、マイクロソフトの公式ドキュメントが表示されます。


レスポンスモデルのアイデア

独自のレスポンスモデルや例外処理を実装してください。 この例はあくまでも出発点です。どのサービスも、独自の方法で例外を処理する必要があるでしょう。このアプローチでは、例外処理を柔軟に制御し、 サービスから適切なレスポンスを返すことができます。

エラーレスポンスモデルの例(参考までに)

public class MyErrorResponse
{
    public string Type { get; set; }
    public string Message { get; set; }
    public string StackTrace { get; set; }

    public MyErrorResponse(Exception ex)
    {
        Type = ex.GetType().Name;
        Message = ex.Message;
        StackTrace = ex.ToString();
    }
}

よりシンプルなサービスでは、次のようなhttpステータスコード例外を実装するとよいでしょう。

public class HttpStatusException : Exception
{
    public HttpStatusCode Status { get; private set; }

    public HttpStatusException(HttpStatusCode status, string msg) : base(msg)
    {
        Status = status;
    }
}

このようにどこからでも投げることができる。

throw new HttpStatusCodeException(HttpStatusCode.NotFound, "User not found");

そうすると、処理するコードはこれだけに簡略化されます。

if (exception is HttpStatusException httpException)
{
    code = (int) httpException.Status;
}


HttpContext.Features.Get<IExceptionHandlerFeature>() WAT?

ASP.NET Coreの開発者は、Auth、MVC、Swaggerなどの機能の異なる側面を分離し、リクエスト処理パイプラインで順次実行するミドルウェアの概念を取り入れました。各ミドルウェアはリクエストコンテキストにアクセスでき、必要に応じてレスポンスに書き込むことができます。例外処理をMVCの外に持ち出すことは、MVC以外のミドルウェアからのエラーをMVCの例外と同じように処理することが重要な場合、理にかなっています(実際のアプリケーションでは非常によくあることだと思います)。例外処理ミドルウェアはMVCの一部ではないので、MVC自身は何も知りませんし、逆に例外処理ミドルウェアは例外がどこから来たのか、もちろんリクエスト実行のパイプのどこかで発生したことは知っていますが、本当のところは知りません。しかし、両者は互いに接続されている必要があります。そこで、例外がどこにも発生しない場合は、例外処理ミドルウェアが例外をキャッチして、パイプラインに登録されているルートを再実行します。このように、例外処理を一貫してMVCに戻すことができます。 コンテントネゴシエーション などのミドルウェアが必要です。例外そのものは、共通のミドルウェアのコンテキストから抽出されます。見た目はおかしいですが、これで仕事は完了です :) 。