我目前正在学习 ASP.NET Core Web API 项目中的错误错误管理,以尝试找到一个优雅的解决方案,确保ProblemDetails
在出现问题时始终返回。
我正在使用WeatherForecast
创建带有控制器的新 C# / ASP.NET Core 8 Web API 项目时创建的默认 API 模板。
我已经实现了自定义异常处理程序;然而我发现它并不总是有效。
当我使用 调用 APIPostman
导致错误时,我得到了正确的ProblemDetails
、状态代码和标头信息;但是,当我从界面中的“试用”功能调用 API 时Swagger
,我得到了错误的状态代码(500 内部错误)以及异常详细信息而不是ProblemDetails
和错误的标头信息。
研究这个问题,我发现在我的GlobalExceptionHandler
类(实现IExceptionHandle
接口)中,如果我尝试来自 Swagger 的代码,该ProblemDetailsService.TryWriteAsync(...)
方法就会返回false
;但是,当我通过从 Postman 调用它执行完全相同的代码时,该ProblemDetailsService.TryWriteAsync(...)
方法就会返回true
。
我提出了一个当“ProblemDetailsService.TryWriteAsync(...)”返回失败的情况,以便我可以尝试直接将其ProblemDetailsContext
写入httpContext.Response
。
当我这样做时,我能够捕获以下异常(再次,这仅在使用 Swagger 的 Try Code 功能时发生......而不是从 Postman 调用时发生):
不支持对“System.Type”实例进行序列化和反序列化。路径:$.HttpContext.Features.Key。”
我不知道为什么这会失败。
我正在寻求帮助,如何确保无论如何调用 API,我的错误管理都以相同的方式运行。
GlobalExceptionHandler
这是我的课程的实现:
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly IProblemDetailsService _problemDetailsService;
public GlobalExceptionHandler(IProblemDetailsService problemDetailsService)
{
if (problemDetailsService == null) throw new ArgumentException(nameof(problemDetailsService));
_problemDetailsService = problemDetailsService;
}
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
var message = exception switch
{
ArgumentException => ((ArgumentException)exception).Message,
ValidationException => ((ValidationException)exception).Message,
_ => $"Internal Server Error ({exception.GetType().Name})"
};
int status = exception switch
{
ArgumentException => (int)HttpStatusCode.BadRequest,
ValidationException => (int)HttpStatusCode.BadRequest,
_ => (int)HttpStatusCode.InternalServerError
};
ProblemDetails problemDetails = new()
{
Title = $"Bad Request: {exception.GetType().Name}", // human-readable summary of the problem type
Detail = message, //detailed explanation specific to the problem
Status = status,
Instance = httpContext.Request.GetEncodedUrl(),
Type = exception.HelpLink
};
var errorcontext = new ProblemDetailsContext()
{
HttpContext = httpContext,
ProblemDetails = problemDetails,
//Exception = exception,
AdditionalMetadata = null
};
httpContext.Response.Clear();
httpContext.Response.StatusCode = status;
var written = await _problemDetailsService.TryWriteAsync(errorcontext);
if (!written)
{
try
{
await httpContext.Response.WriteAsJsonAsync<ProblemDetailsContext>(errorcontext);
written = true;
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
written = false;
}
}
return written;
}
}
这是我的Program.cs
:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Net;
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.Net.Http;
using Microsoft.AspNetCore.Diagnostics;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Add services to the container.
builder.Services.AddExceptionHandler<ProblemDetailsScratchPad.GlobalExceptionHandler>(); // Using a custom exception handler that converts the exception into a Problem Details
builder.Services.AddProblemDetails(); // required for using the exception handler with the custom problem details exception handler code because it adds the services required for Problem Details
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseExceptionHandler(opt => { }); //adding the custom GlobalExceptionHandler to the app's pipeline
app.UseAuthorization();
app.MapControllers();
app.Run();
这是我的实现WeatherForecastController
(并不是说调用 post 或 get 会引发异常并由自定义异常处理程序进行处理)
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet("GetWeatherForecast")]
public ActionResult<IEnumerable<object>> GetWeatherForecast()
{
throw new ArgumentException("Please provide a valid value for a week day: between 0 and 6");
}
[HttpPost("PostWeatherForecastDTO")]
public ActionResult<IEnumerable<object>> PostWeatherForecastDTO()
{
throw new Exception("Please provide a valid value for a week day: between 0 and 6");
}
}