在ASP.NET Core跨平台應用程序開發中如何捕獲並處理全局異常


問題描述

在傳統的ASP.NET Web Api 應用程序開發中,我們處理全局異常的方法通常是實現一個ExceptionFilterAttribute的子類,如下:

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過濾器中注冊這個ErrorHandlingFilter自定義的錯誤處理屬性類,如:

config.Filter.Add(new ErrorHandlingFilter());

 

這樣,在ASP.NET Web Api 應用程序就可以捕獲全局的錯誤並進行處理。

但在ASP.NET Core Web Api的應用程序開發開,以上的解決方案就不起作用了。ASP.NET Core Web Api的全局錯誤捕獲與處理方式與ASP.NET Web Api應用程序的處理方式並非一樣。

所以,我們得使用適合ASP.NET Core Web Api的全局錯誤捕獲與處理的解決方案。那么,有哪些方案可以捕獲和處理ASP.NET Core Web Api應用程序的全局錯誤呢?

方案一

一個比較好並且推薦的做法是使用中間件(middleware)來處理。在ASP.NET Core Web Api應用程序開發中,中間件是最好的解決方案。它不僅可以處理應用程序的異常,同時也可以捕獲過濾器的異常並且也能創建自定義的響應返回結果(比如json的響應返回結果),以下是一個基於ASP.NET Core Web Api的異常處理的中間件:

public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate next;

    public ErrorHandlingMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task Invoke(HttpContext context /* other dependencies */)
    {
        try
        {
            await next(context);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, ex);
        }
    }

    private static Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        var code = HttpStatusCode.InternalServerError; // 500 if unexpected

        if      (exception is MyNotFoundException)     code = HttpStatusCode.NotFound;
        else if (exception is MyUnauthorizedException) code = HttpStatusCode.Unauthorized;
        else if (exception is MyException)             code = HttpStatusCode.BadRequest;

        var result = JsonConvert.SerializeObject(new { error = exception.Message });
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = (int)code;
        return context.Response.WriteAsync(result);
    }
}

 

使用方法,Startup.cs類文件中,在調用MVC之前注冊錯誤處理中間件ErrorHandlingMiddleware,如:

app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc();

 

如果中間件捕獲到應用程序的異常,則會返回類似如下的響應結果:

{ "error": "Authentication token is not valid." }

 

在中間件ErrorHandlingMiddleware的方法HandleExceptionAsync中,可以對當前上下文的錯誤進行任意處理(比如,跟蹤詳細的錯誤信息,記錄錯誤日志等等)。

方案二

在ASP.NET Core 2或者以上的.NET Core 版本中,我們可以使用UseExceptionHandler擴展方法來捕獲並處理ASP.NET Core的全局錯誤。

首頁,在ASP.NET Core 2的Startup.cs文件中注冊錯誤處理擴展方法並指定一個錯誤處理頁面(這里是/Error),如下:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment()) {
        // 開發模式...
    } else {
        app.UseStatusCodePagesWithReExecute("/Error");
        app.UseExceptionHandler("/Error");
    }
    // 其他...
}

 

接着,定義一個可以指定響應狀態碼(HttpStatusCode)的異常類,如下:

public class HttpException : Exception
{
    public HttpException(HttpStatusCode statusCode) { StatusCode = statusCode; }
    public HttpStatusCode StatusCode { get; private set; }
}

 

最后,在控制器中創建上文提到的/Error錯誤處理頁面,ASP.NET Core 2捕獲到的錯誤將轉到這個錯誤處理頁面。在這個頁面,我們就可以處理這些錯誤/異常,如下:

[AllowAnonymous]
public IActionResult Error()
{
    // 捕獲狀態碼
    var statusCode = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error is HttpException httpEx ?
        httpEx.StatusCode : (HttpStatusCode)Response.StatusCode;

    // 如果是ASP.NET Core Web Api 應用程序,直接返回狀態碼(不跳轉到錯誤頁面,這里假設所有API接口的路徑都是以/api/開始的)
    if (HttpContext.Features.Get<IHttpRequestFeature>().RawTarget.StartsWith("/api/", StringComparison.Ordinal))
        return StatusCode((int)statusCode);

    // 創建一個友好的錯誤處理頁面視圖模型.
    string text = null;
    switch (statusCode) {
        case HttpStatusCode.NotFound: text = "Page not found."; break;
        // 其他詳細信息
    }
    return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, ErrorText = text });
}

 

注: 如果需要讓 API 接口返回json對象,則可以使用 return Json(...)來替換return StatusCode(...)

方案三

在ASP.NET Core 2.1+版本中,我們可以使用中間件Community.AspNetCore.ExceptionHandling.Mvc[1]來處理,如下:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddExceptionHandlingPolicies(options =>
    {
        options.For<InitializationException>().Rethrow();

        options.For<SomeTransientException>().Retry(ro => ro.MaxRetryCount = 2).NextPolicy();

        options.For<SomeBadRequestException>()
        .Response(e => 400)
            .Headers((h, e) => h["X-MyCustomHeader"] = e.Message)
            .WithBody((req,sw, exception) =>
                {
                    byte[] array = Encoding.UTF8.GetBytes(exception.ToString());
                    return sw.WriteAsync(array, 0, array.Length);
                })
        .NextPolicy();

        // 確保所有的異常均被捕獲
        options.For<Exception>()
        .Log(lo =>
            {
                lo.EventIdFactory = (c, e) => new EventId(123, "UnhandlerException");
                lo.Category = (context, exception) => "MyCategory";
            })
        .Response(null, ResponseAlreadyStartedBehaviour.GoToNextHandler)
            .ClearCacheHeaders()
            .WithObjectResult((r, e) => new { msg = e.Message, path = r.Path })
        .Handled();
    });
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseExceptionHandlingPolicies();
    app.UseMvc();
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM