web-dev-qa-db-ja.com

ASP.NET Web API 2で*すべての*例外処理を無効にします(自分用のスペースを作るため)?

次のようなミドルウェアコンポーネントで例外処理を結び付けたいです。

_public override async Task Invoke(IOwinContext context)
{
    try
    {
        await Next.Invoke(context);
    }
    catch (Exception ex)
    {
        // Log error and return 500 response
    }
}
_

しかし、私がキャッチしたい例外のいくつかは、取得する前にWeb APIパイプラインによってキャッチされ、HttpErrorResponsesに変換されます。その過程で、エラーに関する多くの詳細が失われるため、デバッグなどのときに有用なスタックトレースを取得できません(例外がスローされてもデバッガーは停止しません-コードを手動でステップ実行して確認する必要があります)それが失敗する場所...).

次の実装でカスタム例外ハンドラを追加してみました。

_public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
{
    var owinContext = context.Request.GetOwinContext();
    owinContext.Set(Constants.ContextKeys.Exception, context.Exception);
    return Task.FromResult(0);
}
_

スタートアップ構成でconfig.Services.Replace(typeof(IExceptionHandler), new MyExceptionHandler());を介して登録されていますが、Next.Invoke(context)を実行した後にそれを確認しています

_context.Get<Exception>(Constants.ContextKeys.Exception);
_

stillは、必要な詳細をすべて提供するわけではなく、デバッガーで障害点で停止することに失敗します。

私ができる方法はありますか完全にオフにするall組み込みのエラー処理で、自分のミドルウェアがそれを処理できるようにしますか?

説明、多くの人が私が何を求めているのか誤解しているようです:

  • Web APIの組み込みエラー処理はsome(すべてではない)例外をキャッチし、それらを500応答に書き換えます。
  • 私はall例外をキャッチし、いくつかのロギングを行い、then 500応答を出力します選択した情報を使用して(それらのほとんどについて、次を参照してください)箇条書き)。
  • ビジネスロジックの障害を通知するいくつかの例外もあり、その場合は代わりに40xエラーを返します。
  • これを(アプリ)パイプラインの最上部、つまりラッピングeverythingでリクエストライフサイクル内に配置したい
  • 私はOWINを使用してこれを処理し、将来のセルフホストシナリオに移植できるようにします(つまり、このアプリは常にIIS-HTTPモジュール、グローバルでホストされることになるわけではありません) .asax.cs et alはここでは関係ありません)。
43
Tomas Aschan

更新私はこれについてブログに書いた 。ブログ投稿を調査したところ、改善の余地がありました。この回答の関連部分を更新しました。これが他のすべての提案よりも優れていると思う理由、またはデフォルトの動作の詳細については、投稿全体をお読みください:)


私は今、私が探していたものに100%準拠していなくても、うまくいくように見える次のアプローチを行っています:

  • クラスを作成するPassthroughExceptionHandler

    public class PassthroughExceptionHandler : IExceptionHandler
    {
        public Task HandleAsync(ExceptionHandlerContext context, CancellationToken cancellationToken)
        {
            // don't just throw the exception; that will ruin the stack trace
            var info = ExceptionDispatchInfo.Capture(context.Exception);
            info.Throw();
            return Task.CompletedTask;
        }
    }
    
  • そのクラスをWeb APIのIExceptionHandlerサービスに置き換えます

    config.Services.Replace(typeof(IExceptionHandler), new PassthroughExceptionHandler());
    
  • 私が望むことをするミドルウェアクラスを作成します:

    public class ExceptionHandlerMiddleware
    {
        public override async Task Invoke(IOwinContext context)
        {
            try
            {
                await Next?.Invoke(context);
            }
            catch (Exception ex)
            {
                // handle and/or log
            }
        }
    }
    
  • 最初にそのミドルウェアをスタックに登録します。

    app.Use<ExceptionHandlerMiddleware>()
       .UseStageMarker(PipelineStage.Authenticate)
       // other middlewares omitted for brevity
       .UseStageMarker(PipelineStage.PreHandlerExecute)
       .UseWebApi(config);
    

思いついた人には誰でも賞金を差し上げます (賞金の期限が切れました...)私はまだより良い解決策を探しています。たとえば、unhandled例外がスローされたときにブレークします。 (このアプローチでは、ハンドラーで例外を再スローするとVSが中断しますが、元の呼び出しスタックは失われます。例外がスローされたときに状態をインターセプトできるようにするには、障害のある行にブレークポイントを設定して再度デバッグする必要があります。)

18
Tomas Aschan

独自のコントローラーアクティベーターを作成し、カスタム例外ハンドラーを用意して、ExceptionFilterAttributeを使用することもできます。

  1. コントローラーアクティベーターを作成する

    public class ExceptionHandlingControllerActivator : IHttpControllerActivator
    {
        private readonly IHttpControllerActivator _concreteActivator;
    
        public ExceptionHandlingControllerActivator(IHttpControllerActivator concreteActivator)
        {
            _concreteActivator = concreteActivator;
        }
    
        public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
        {
            try
            {
                return _concreteActivator.Create(request, controllerDescriptor, controllerType);
            }
            catch (Exception ex)
            {
                // do stuff with the exception
                throw new HttpResponseException(request.CreateResponse(HttpStatusCode.InternalServerError, new ResponseModel(ex)));
            }
        }
    }
    
  2. ExceptionFilterAttributeを作成する

    public class ExceptionHandlingFilter : ExceptionFilterAttribute
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            // do stuff with the exception
    
            var request = context.Request;
            ResponseModel respMod = null;
    
            // Example: if debug constant is not defined, mask exception, otherwise create normal object with message, inner exception and stacktrace
    
            #if !DEBUG
            respMod = new ResponseModel(context.Exception, context.Exception.Message, true);
            #else
            respMod = new ResponseModel(context.Exception);
            #endif
    
            context.Response = request.CreateResponse(HttpStatusCode.InternalServerError, respMod);
        }
    }
    
  3. ResponseModelは、フォーマッターを使用してJSONにシリアル化し、すべてのコントローラー応答によって返されるクラスです。そのため、クライアントは、HTTPステータスコードに加えて、エラーデータと成功した応答を識別できます。

    config.Formatters.Clear(); // do not need any other
    config.Formatters.Add(new JsonMediaTypeFormatter());
    
  4. ワイヤーアップ

    // ... [cut] ...            
    config.Filters.Add(new ExceptionHandlingFilter());
    // ... [cut] ...
    config.Services.Replace(typeof(IHttpControllerActivator),
        new ExceptionHandlingControllerActivator(config.Services.GetHttpControllerActivator())
    );
    // ... [cut] ...
    app.UseWebApi(config);
    
1

これでうまくいくかどうかはわかりませんが、見つからないエラーでもすべてのエラーをJSONとして返送するという同様の要件があります。ベースコントローラーを作成し、ExecuteAsyncをオーバーライドして、独自の応答を作成できるようにしました。

public class ControllerBase : ApiController
{
    protected string ClassName = "ControllerBase::";

    public override System.Threading.Tasks.Task<HttpResponseMessage> ExecuteAsync(System.Web.Http.Controllers.HttpControllerContext controllerContext, System.Threading.CancellationToken cancellationToken)
    {
        try
        {
            System.Threading.Tasks.Task<HttpResponseMessage> TaskList = base.ExecuteAsync(controllerContext, cancellationToken);

            if (TaskList.Exception != null && TaskList.Exception.GetBaseException() != null)
            {
                JSONErrorResponse AsyncError = new JSONErrorResponse();
                AsyncError.ExceptionMessage = TaskList.Exception.GetBaseException().Message;
                AsyncError.ErrorMessage = string.Format("Unknown error {0} ExecuteAsync {1}", ClassName ,controllerContext.Request.RequestUri.AbsolutePath);
                AsyncError.HttpErrorCode = HttpStatusCode.BadRequest;

                HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(AsyncError.HttpErrorCode, AsyncError);

                return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
            }
            return TaskList;
        }
        catch (Exception Error)
        {
            JSONErrorResponse BadParameters = new JSONErrorResponse();
            BadParameters.ExceptionMessage = Error.Message;
            BadParameters.ErrorMessage = string.Format("Method [{0}], or URL [{1}] not found, verify your request", controllerContext.Request.Method.Method, controllerContext.Request.RequestUri.AbsolutePath);
            BadParameters.HttpErrorCode = HttpStatusCode.NotFound;
            HttpResponseMessage ErrorResponse = controllerContext.Request.CreateResponse(BadParameters.HttpErrorCode, BadParameters);

            return System.Threading.Tasks.Task.Run<HttpResponseMessage>(() => ErrorResponse);
        }
    }
}

public class JSONErrorResponse
{
    //Possible message from exception
    public string ExceptionMessage { get; set; }
    //Possible custom error message
    public string ErrorMessage { get; set; }
    //Http error code
    public HttpStatusCode HttpErrorCode { get; set; }
}

Web apiには独自のエラー処理が組み込まれているため、OWINはそのような例外を処理することは想定されていません。OWINはアプリケーションとは別に設計されています。例外ハンドラーのHandleAsyncメソッドにブレークポイントを設定すると、コンテキスト変数を検査して、例外の詳細を確認できるはずです。

デバッグ目的でこれを実行しようとしている場合は、そこにブレークポイントを設定すると、例外を確認できるはずです。例外をログに記録する必要がある場合は、例外ハンドラーがそのための最良の場所だと私は考えています。

お役に立てば幸いです。

0
MichaelDotKnox

ここに役立つかもしれないものがあります:

https://stackoverflow.com/a/21382651/141985

http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21#global-error

基本的に、エラーをキャッチ、処理、変更するためのサポートが組み込まれています。

次のようになります。

public class ExceptionLogger : System.Web.Http.ExceptionHandling.ExceptionLogger
{
    Logger _logger;

    public ExceptionLogger(Logger logger)
    {
        _logger = logger;
    }

    public override void Log(ExceptionLoggerContext context)
    {
        _logger.Error(context.ExceptionContext.Exception.ToString());
    }
}
0
hatcyl