.NET API 上的自定义异常处理
例外是不好的,我们知道吗?但如果我们必须处理它们怎么办?
当我们遇到异常时会发生什么,例如,在 API 上,它会显示一条堆栈消息,其中包含我们可能希望从用户获得的响应中删除的大量信息。
对于演示,我创建了一个 dotnet API 并添加了一个会引发异常的方法:
[HttpGet]
[Route("GetWithoutExceptionHandler")]
public Task GetWithoutExceptionHandler()
{
throw new Exception("This is a custom exception!");
}
如果我们抛出异常,它看起来像这样:
System.Exception: This is a custom exception!
at CustomExceptionHandleDemo.Controllers.WeatherForecastController.GetWithoutExceptionHandler()
at lambda_method16(Closure , Object , Object[] )
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
这看起来很正常,这是你从异常中得到的,但对我来说,它显示了很多信息,如果你有库和东西,它可以向可能试图查看东西的人显示有关你的客户、你的项目或其他东西的明智信息。
这是 Swagger 上的样子:

创建自定义异常
这一步可以避免,因为我们知道会抛出什么异常,在本例中是 Exception,但对我来说,拥有自定义异常更好,因为您可以更好地控制抛出的内容。
在本例中,我刚刚创建了一个继承自 Exception 的对象 CustomException:
namespace CustomExceptionHandleDemo.Exceptions
{
public class CustomException : Exception
{
/// <summary>
/// Constructor for <see cref="CustomException"/>
/// </summary>
public CustomException() { }
/// <summary>
/// Constructor for <see cref="CustomException"/>
/// </summary>
/// <param cref="string" name="message">Parameter for message</param>
public CustomException(string message) : base(message) { }
/// <summary>
/// Constructor for <see cref="CustomException"/>
/// </summary>
/// <param cref="string" name="message">Parameter for message</param>
/// <param cref="Exception" name="inner">Parameter for inner</param>
public CustomException(string message, Exception inner) : base(message, inner) { }
}
}
创建自定义异常后,让我们更新方法以抛出 CustomException 而不是 Exception:
[HttpGet]
[Route("GetWithoutExceptionHandler")]
public Task GetWithoutExceptionHandler()
{
throw new CustomException("This is a custom exception!");
}
目前这不会改变任何东西,但堆栈跟踪将显示抛出的对象是 CustomException 而不是 Exception,看一下堆栈跟踪的开头:
CustomExceptionHandleDemo.Exceptions.CustomException: This is a custom exception!
at CustomExceptionHandleDemo.Controllers.WeatherForecastController.GetWithoutExceptionHandler()
at lambda_method24(Closure , Object , Object[] )
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
创建 ExceptionFilterAttribute
微软为我们提供了一种在抛出异常后处理异常的方法,您可以在[此处](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filters.exceptionfilterattribute?view=aspnetcore-7.0查看更多信息。
但到目前为止,他们给我们的文档是:
一个抽象过滤器,在操作抛出异常后异步运行。子类必须重写 OnException(ExceptionContext) 或 OnExceptionAsync(ExceptionContext),但不能同时重写两者。
那么让我们创建一个,我们将创建 CustomExceptionFilterAttribute,其中我们将覆盖 OnException:
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using CustomExceptionHandleDemo.Exceptions;
namespace CustomExceptionHandleDemo.ExceptionFilterAttributes
{
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
/// <summary>
/// OnException
/// </summary>
/// <param cref="ExceptionContext" name="context">Parameter for context</param>
public override void OnException(ExceptionContext context)
{
if (context.Exception is CustomException)
{
context.HttpContext.Response.StatusCode = 500;
context.Result = new ObjectResult(context.Exception.Message);
}
}
}
}
正如你所看到的,我们正在查看 ExceptionContext,当异常是 CustomException 类型时,我们会做一些事情。
这个东西正在更新我们要返回的响应和状态代码。
为了更新状态代码,我们必须更新 context.HttpContext.Response.StatusCode,为了返回结果,我们必须通过为 context.Result 提供一个继承自 ActionResult 的对象来更新 context.Result。
这是一个过滤器,所以这意味着我们必须通过添加 [CustomExceptionFilter] 来添加一些东西。
使用过滤器
现在,让我们复制我们拥有的方法并添加此过滤器,以便它采取行动,我们的 API 端点最终将如下所示:
using CustomExceptionHandleDemo.ExceptionFilterAttributes;
using CustomExceptionHandleDemo.Exceptions;
using Microsoft.AspNetCore.Mvc;
namespace CustomExceptionHandleDemo.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
public WeatherForecastController()
{
}
[HttpGet]
[Route("GetWithoutExceptionHandler")]
public Task GetWithoutExceptionHandler()
{
throw new CustomException("This is a custom exception!");
}
[HttpGet]
[Route("GetWithExceptionHandler")]
[CustomExceptionFilter]
public Task GetWithExceptionHandler()
{
throw new CustomException("This is a custom exception!");
}
}
}
如您所见,我们有一个名为 GetWithExceptionHandler 的新方法,其逻辑与 GetWithoutExceptionHandler 相同,但在本例中,我们将过滤器 [CustomExceptionFilter] 添加到方法中。
运行该方法后,结果如下,我将显示一个图像,因为它不再显示堆栈跟踪:
因此,我们创建了一个自定义异常,一个过滤器来覆盖当我们抛出异常并在方法上使用它时发生的情况。
这可以用于很多事情,例如记录和了解错误发生的内容、时间和地点。