我使用ASP。NET核心为我的新的REST API项目后使用常规的ASP。NET Web API很多年了。我看不出在ASP中有什么处理异常的好方法。NET核心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
        };
    }
}

这是我的启动过滤器注册:

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

我遇到的问题是,当我的AuthorizationFilter发生异常时,它没有被ErrorHandlingFilter处理。我希望它能像以前的ASP一样被捕获。NET Web API。

那么我如何捕捉所有应用程序异常以及任何异常从动作过滤器?


当前回答

如果你想为特定的控制器设置自定义异常处理行为,你可以通过覆盖控制器的OnActionExecuted方法来实现。

记住将ExceptionHandled属性设置为true以禁用默认的异常处理行为。

下面是我正在编写的api的一个示例,我想捕捉特定类型的异常并返回json格式的结果:

    private static readonly Type[] API_CATCH_EXCEPTIONS = new Type[]
    {
        typeof(InvalidOperationException),
        typeof(ValidationException)           
    };

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        base.OnActionExecuted(context);

        if (context.Exception != null)
        {
            var exType = context.Exception.GetType();
            if (API_CATCH_EXCEPTIONS.Any(type => exType == type || exType.IsSubclassOf(type)))
            {
                context.Result = Problem(detail: context.Exception.Message);
                context.ExceptionHandled = true;
            }
        }  
    }

其他回答

有一个内置的中间件:

ASP。NET Core 5版本:

app.UseExceptionHandler(a => a.Run(async context =>
{
    var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
    var exception = exceptionHandlerPathFeature.Error;
    
    await context.Response.WriteAsJsonAsync(new { error = exception.Message });
}));

旧版本(他们没有WriteAsJsonAsync扩展):

app.UseExceptionHandler(a => a.Run(async context =>
{
    var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
    var exception = exceptionHandlerPathFeature.Error;
    
    var result = JsonConvert.SerializeObject(new { error = exception.Message });
    context.Response.ContentType = "application/json";
    await context.Response.WriteAsync(result);
}));

它应该做几乎相同的事情,只是要编写的代码少了一点。

重要:记住将它添加在MapControllers \ UseMvc(或。net Core 3中的UseRouting)之前,因为顺序很重要。

首先,配置ASP。NET Core 2启动以重新执行到错误页面,以处理来自web服务器的任何错误和任何未处理的异常。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment()) {
        // Debug config here...
    } else {
        app.UseStatusCodePagesWithReExecute("/Error");
        app.UseExceptionHandler("/Error");
    }
    // More config...
}

接下来,定义一个异常类型,允许您使用HTTP状态代码抛出错误。

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

最后,在错误页面的控制器中,根据错误的原因以及最终用户是否可以直接看到响应,定制响应。这段代码假设所有API url都以/ API /开头。

[AllowAnonymous]
public IActionResult Error()
{
    // Gets the status code from the exception or web server.
    var statusCode = HttpContext.Features.Get<IExceptionHandlerFeature>()?.Error is HttpException httpEx ?
        httpEx.StatusCode : (HttpStatusCode)Response.StatusCode;

    // For API errors, responds with just the status code (no page).
    if (HttpContext.Features.Get<IHttpRequestFeature>().RawTarget.StartsWith("/api/", StringComparison.Ordinal))
        return StatusCode((int)statusCode);

    // Creates a view model for a user-friendly error page.
    string text = null;
    switch (statusCode) {
        case HttpStatusCode.NotFound: text = "Page not found."; break;
        // Add more as desired.
    }
    return View("Error", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, ErrorText = text });
}

ASP。NET Core将记录用于调试的错误细节,因此状态代码可能是您想要提供给(潜在的不受信任的)请求者的所有信息。如果你想要显示更多信息,你可以增强HttpException来提供它。对于API错误,您可以将json编码的错误信息放在消息体中,方法是替换返回StatusCode…返回Json....

通过添加你自己的“异常处理中间件”,很难重用一些良好的内置异常处理逻辑,比如在错误发生时向客户端发送一个“符合RFC 7807的有效负载”。

我所做的是在Startup.cs类之外扩展内置的异常处理程序,以处理自定义异常或覆盖现有异常的行为。例如,在不改变其他异常默认行为的情况下,将ArgumentException转换为BadRequest:

在Startup.cs上添加:

app.UseExceptionHandler("/error");

并像这样扩展ErrorController.cs:

using System;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Hosting;

namespace Api.Controllers
{
    [ApiController]
    [ApiExplorerSettings(IgnoreApi = true)]
    [AllowAnonymous]
    public class ErrorController : ControllerBase
    {
        [Route("/error")]
        public IActionResult Error(
            [FromServices] IWebHostEnvironment webHostEnvironment)
        {
            var context = HttpContext.Features.Get<IExceptionHandlerFeature>();
            var exceptionType = context.Error.GetType();
            
            if (exceptionType == typeof(ArgumentException)
                || exceptionType == typeof(ArgumentNullException)
                || exceptionType == typeof(ArgumentOutOfRangeException))
            {
                if (webHostEnvironment.IsDevelopment())
                {
                    return ValidationProblem(
                        context.Error.StackTrace,
                        title: context.Error.Message);
                }

                return ValidationProblem(context.Error.Message);
            }

            if (exceptionType == typeof(NotFoundException))
            {
                return NotFound(context.Error.Message);
            }

            if (webHostEnvironment.IsDevelopment())
            {
                return Problem(
                    context.Error.StackTrace,
                    title: context.Error.Message
                    );
            }
            
            return Problem();
        }
    }
}

注意:

NotFoundException是一个自定义异常,所有你需要做的是抛出新的NotFoundException(null);或抛出新的ArgumentException(“无效参数。”); 您不应该向客户端提供敏感的错误信息。服务错误是一种安全风险。

要配置每种异常类型的异常处理行为,您可以使用NuGet包中的中间件:

Community.AspNetCore.ExceptionHandling.NewtonsoftJson ASP。NET Core 2.0 ASP. aspnetcore . exceptionhandling . mvcNET Core 2.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();

        // Ensure that all exception types are handled by adding handler for generic exception at the end.
        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();
}

在任何特定方法上处理异常的简单方法是:

using Microsoft.AspNetCore.Http;
...

public ActionResult MyAPIMethod()
{
    try
    {
       var myObject = ... something;

       return Json(myObject);
    }
    catch (Exception ex)
    {
        Log.Error($"Error: {ex.Message}");
        return StatusCode(StatusCodes.Status500InternalServerError);
    }         
}