Results.Problem 是 ASP.NET Core 提供的一种标准化错误响应生成方法,用于创建符合 RFC 7807 标准的错误响应(Problem Details)。它可用于 Web API 中返回结构化的错误信息,包含 type、title、status 和 detail 等字段,帮助客户端理解并处理错误。通过参数定制,Results.Problem 能清晰地描述错误类型、状态码、详细信息和实例路径,提升了 API 的一致性和可维护性,使客户端处理错误更加高效,特别适合构建现代化 RESTful API。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/test", () =>
{
return Results.Problem(type: "Bad Request",
title: "无效的编号",
detail: "编号格式为:N00000,N+5位数字",
statusCode: StatusCodes.Status400BadRequest);
});
app.Run();
运行结果:
下面的代码展示了在 ASP.NET Core 中通过 Results.Problem 扩展来生成自定义的标准化错误响应。此扩展使用了 Problem Details 格式,并自定义了错误细节的处理方式,增强了错误信息的结构化和调试性。
首先,通过 builder.Services.AddProblemDetails 注册了 Problem Details 服务,并在 CustomizeProblemDetails 配置中自定义了 Problem Details 的内容。在此配置中,指定了错误实例 (Instance) 的详细格式,包括请求协议、方法和路径。同时,添加了客户端请求的跟踪信息,如 requestId(请求唯一标识符)、traceId(跟踪标识符)和 clientIP(客户端 IP 地址),使错误响应更具上下文信息。
为捕获和处理自定义异常类型,代码创建了一个名为 ProblemException 的自定义异常类,该类包含 Error 和 Message 属性,用于存储特定的错误信息。接着,实现了 IExceptionHandler 接口的 ProblemExceptionHandler 类,该类负责拦截异常,并通过 ProblemDetailsService 生成标准的 Problem Details 响应。在此过程中,TryHandleAsync 方法捕获并检测是否为 ProblemException 异常,如果是,则创建一个自定义的 ProblemDetails 对象,将 Status 设置为 400(Bad Request),并根据 ProblemException 实例的 Error 和 Message 属性填充 Title 和 Detail 字段。最后,通过 ProblemDetailsService.TryWriteAsync 方法将 Problem Details 写入响应,以确保客户端接收到结构化的错误信息。
在应用程序中,定义了一个触发 ProblemException 异常的路由 /test,当客户端请求该路由时,抛出一个 ProblemException 异常,返回包含自定义错误信息的 Problem Details 响应。通过这种方式,客户端可以接收到详细的错误上下文和结构化的错误信息,有助于快速识别和调试问题。
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails(opt =>
{
opt.CustomizeProblemDetails = context =>
{
context.ProblemDetails.Instance = $"{context.HttpContext.Request.Protocol} {context.HttpContext.Request.Method} {context.HttpContext.Request.Path}";
context.ProblemDetails.Extensions.TryAdd("requestId", context.HttpContext.TraceIdentifier);
var activity = context.HttpContext.Features.Get<IHttpActivityFeature>()?.Activity;
context.ProblemDetails.Extensions.TryAdd("traceId", activity?.Id);
if (context.HttpContext.Connection != null && context.HttpContext.Connection.RemoteIpAddress != null)
{
context.ProblemDetails.Extensions.TryAdd("clientIP", context.HttpContext.Connection.RemoteIpAddress?.ToString());
}
};
});
builder.Services.AddExceptionHandler<ProblemExceptionHandler>();
var app = builder.Build();
app.MapGet("/test", () =>
{
throw new ProblemException(error: "无效的编号",
message: "编号格式为:N00000,N+5位数字");
});
app.UseExceptionHandler();
app.Run();
public class ProblemException : Exception
{
public string Error { get; }
public ProblemException(string error, string message) : base(message)
{
Error = error;
}
}
public class ProblemExceptionHandler : IExceptionHandler
{
private readonly IProblemDetailsService _problemDetailsService;
public ProblemExceptionHandler(IProblemDetailsService problemDetailsService)
{
_problemDetailsService = problemDetailsService;
}
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext, Exception exception, CancellationToken cancellationToken)
{
if (exception is not ProblemException problemException)
{
return true;
}
var problemDetails = new ProblemDetails
{
Status = StatusCodes.Status400BadRequest,
Title = problemException.Error,
Detail = problemException.Message,
Type = "Bad Request"
};
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
return await _problemDetailsService.TryWriteAsync(new ProblemDetailsContext
{
HttpContext = httpContext,
ProblemDetails = problemDetails
});
}
}
实现效果: