我使用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。

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


当前回答

有一个内置的中间件:

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)之前,因为顺序很重要。

其他回答

要配置每种异常类型的异常处理行为,您可以使用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();
}

如果你想为特定的控制器设置自定义异常处理行为,你可以通过覆盖控制器的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;
            }
        }  
    }

这个被广泛接受的答案对我帮助很大,但是我想在中间件中传递HttpStatusCode,以便在运行时管理错误状态代码。

根据这个链接,我有一些想法去做同样的事情。所以我把安德烈的答案和这个合并了。所以我的最终代码如下:

1. 基类

public class ErrorDetails
{
    public int StatusCode { get; set; }
    public string Message { get; set; }

    public override string ToString()
    {
        return JsonConvert.SerializeObject(this);
    }
}

2. 自定义异常类类型

public class HttpStatusCodeException : Exception
{
    public HttpStatusCode StatusCode { get; set; }
    public string ContentType { get; set; } = @"text/plain";

    public HttpStatusCodeException(HttpStatusCode statusCode)
    {
        this.StatusCode = statusCode;
    }

    public HttpStatusCodeException(HttpStatusCode statusCode, string message) 
        : base(message)
    {
        this.StatusCode = statusCode;
    }

    public HttpStatusCodeException(HttpStatusCode statusCode, Exception inner) 
        : this(statusCode, inner.ToString()) { }

    public HttpStatusCodeException(HttpStatusCode statusCode, JObject errorObject) 
        : this(statusCode, errorObject.ToString())
    {
        this.ContentType = @"application/json";
    }

}

3.自定义异常中间件

public class CustomExceptionMiddleware
{
    private readonly RequestDelegate next;

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

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

    private Task HandleExceptionAsync(HttpContext context, HttpStatusCodeException exception)
    {
        string result = null;
        context.Response.ContentType = "application/json";
        if (exception is HttpStatusCodeException)
        {
            result = new ErrorDetails() 
            {
                Message = exception.Message,
                StatusCode = (int)exception.StatusCode 
            }.ToString();
            context.Response.StatusCode = (int)exception.StatusCode;
        }
        else
        {
            result = new ErrorDetails() 
            { 
                Message = "Runtime Error",
                StatusCode = (int)HttpStatusCode.BadRequest
            }.ToString();
            context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
        }
        return context.Response.WriteAsync(result);
    }

    private Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        string result = new ErrorDetails() 
        { 
            Message = exception.Message,
            StatusCode = (int)HttpStatusCode.InternalServerError 
        }.ToString();
        context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
        return context.Response.WriteAsync(result);
    }
}

4. 扩展方法

public static void ConfigureCustomExceptionMiddleware(this IApplicationBuilder app)
{
    app.UseMiddleware<CustomExceptionMiddleware>();
}

5. 在startup.cs中配置Method

app.ConfigureCustomExceptionMiddleware();
app.UseMvc();

现在我在帐户控制器中的登录方法:

try
{
    IRepository<UserMaster> obj 
        = new Repository<UserMaster>(_objHeaderCapture, Constants.Tables.UserMaster);
    var result = obj.Get()
        .AsQueryable()
        .Where(sb => sb.EmailId.ToLower() == objData.UserName.ToLower() 
            && sb.Password == objData.Password.ToEncrypt() 
            && sb.Status == (int)StatusType.Active)
        .FirstOrDefault();
    if (result != null)//User Found
        return result;
    else // Not Found
        throw new HttpStatusCodeException(HttpStatusCode.NotFound,
            "Please check username or password");
}
catch (Exception ex)
{
    throw ex;
}

上面你可以看到,如果我没有找到用户,然后引发HttpStatusCodeException,其中我已经传递了HttpStatusCode。中间件中的NotFound状态和自定义消息

catch (HttpStatusCodeException ex)

block将被调用,它将把控制传递给

private Task HandleExceptionAsync HttpStatusCodeException异常)方法

但如果我得到运行时错误之前?为此,我使用了try catch块抛出异常,并将在catch (exception exceptionObj)块中捕获,并将控制传递给

任务HandleExceptionAsync(HttpContext上下文,异常异常)

方法。为了保持一致性,我使用了一个ErrorDetails类。

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

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);
    }         
}

首先,配置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....