在大多数 Web API 项目中,异常处理通常是通过使用中间件、筛选器或使用 mediator 等库来完成的。
基本上,当我们有一个控制器时,我们可以为它们分配不同的操作。正如我们的示例,我们有一个控制器和三个不同的操作。操作可以是 POST、/GET 或 /Delete。
当用户发送请求时,它会直接进入 Controller,Controller 会根据请求的类型将请求路由到 Action。当任何 action 出现错误时,我们可以在 action 中单独处理该错误。它将向用户返回一个可以理解的错误。基本上,对于每个 action,我们都在编写自己的错误处理程序。
单独处理错误
我们可以做到这一点的另一种方法是使用中间件,它类似于在我们的请求的管道中运行的拦截器服务。如果出现任何问题,中间件将捕获它并将错误发送给用户。
使用自定义中间件处理错误
H但是**,在 .NET 8 中,**有一个名为 IExceptionHandler 的新接口用于此目的,它能够注册异常处理程序,还可以使用内置中间件以更优雅的方式处理异常。
使用 IExceptionHandler 处理错误
如果存在无法在操作中捕获的任何类型的错误,IExceptionHandler 中间件将能够全局处理它。它用作通用错误处理程序,这意味着无论向它抛出什么类型的异常,它都可以被捕获。
除此之外,它还将利用问题详细信息,基本上,通过问题详细信息,我们可以向用户返回非常详细的错误,而无需透露所有堆栈托盘。
让我们来看简单的演示。
这里我们有一个简单的 Web API 项目,它有一个用于获取 todos 的控制器。为了实现我们的全局异常处理程序,我创建了一个名为 “ErrorHandler” 的新文件夹,并在该文件夹中创建了一个名为 “GlobalExceptionHanlder” 的新类。
项目结构
创建新类后,我必须首先扩展 IExceptionHandler 接口并实现所有必需的方法。
using Microsoft.AspNetCore.Diagnostics;
namespace DemoProject.ErrorHandler
{
public class GlobalExceptionHandler : IExceptionHandler
{
public ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
}
在这里,我们可以看到有一个参数很少的方法。这些参数在处理不同的异常时将发挥关键作用。
httpContext:监听传入和传出的请求以进行操作
**例外:**引发的异常
现在进行一些更改以创建我们的全局异常。
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.WebUtilities;
using System.Text.Json;
namespace DemoProject.ErrorHandler
{
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger) {
_logger = logger;
}
private const string UnhandledExceptionMsg = "An unhandled exception has occurred while executing the request.";
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
_logger.LogError(exception, exception.Message);
var problemDetails = CreateProblemDetails(httpContext, exception);
var jsonDetails = ConvertToJson(problemDetails);
httpContext.Response.ContentType = "application/problem+json";
await httpContext.Response.WriteAsync(jsonDetails, cancellationToken);
return true;
}
private ProblemDetails CreateProblemDetails(in HttpContext context, in Exception exception)
{
var statusCode = context.Response.StatusCode;
var reasonPhrase = ReasonPhrases.GetReasonPhrase(statusCode);
if (string.IsNullOrEmpty(reasonPhrase))
{
reasonPhrase = UnhandledExceptionMsg;
}
var problemDetails = new ProblemDetails
{
Detail = $"API Error {exception.Message}",
Instance = "API",
Status = statusCode,
Title = reasonPhrase,
};
return problemDetails;
}
private string ConvertToJson(in ProblemDetails problemDetails)
{
try
{
return JsonSerializer.Serialize(problemDetails);
}
catch (Exception ex)
{
const string msg = "An exception has occurred while serializing error to JSON";
_logger.LogError(msg, ex);
}
return string.Empty;
}
}
}
实现我们的异常处理程序后,我们需要将其注入到我们的服务中。为此,我们可以按如下方式更改我们的 “program.cs” 文件。
using DemoProject.ErrorHandler;
using DemoProject.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpClient();
builder.Services.AddScoped<ITodoService, TodoService>();
//Add these services to inject
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
//Add this to use our exception handler
app.UseExceptionHandler();
app.MapControllers();
app.Run();
现在我们可以测试我们的异常。为了测试异常,我只是更改了 API URL 并收到了响应。