c# - 如何在 InvalidModelStateResponseFactory 中获取请求正文?
问题描述
我有一个由外部应用程序调用的 API,我相信我们有很多 400 错误。
我查找了如何捕获和记录自动 HTTP 400 错误,并找到了以下解决方案:
services.AddMvc()
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = context =>
{
ILogger logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger>();
string keys = JsonConvert.SerializeObject(context.ModelState.Keys);
string values = JsonConvert.SerializeObject(context.ModelState.Values);
logger.Debug($"Keys: {keys}, Values: {values}");
return new BadRequestObjectResult(context.ModelState);
};
});
这很好用,但我假设我会得到所有键和所有值,而实际上,它只显示有错误的键,甚至不显示它们发送的值。
有没有办法记录整个请求正文,InvalidModelStateResponseFactory
或者我以错误的方式查看它?
请注意,我不想使用该SuppressModelStateInvalidFilter
选项。
解决方案
多次读取请求正文需要缓冲并将其存储在某处,而不仅仅是在它到达时进行流式传输(记住模型绑定器已经读取过一次)。请注意,这样做会对性能产生负面影响,因为请求需要存储在内存中(较大的请求在磁盘上)。我选择仅在非生产实例中启用此功能。这是我如何让它工作的:
首先,您需要告诉请求您将多次需要它。这是我改编自这个答案的中间件
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
public sealed class RequestBodyRewindMiddleware
{
readonly RequestDelegate _next;
public RequestBodyRewindMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
try { context.Request.EnableBuffering(); } catch { }
await _next(context);
}
}
public static class BodyRewindExtensions
{
public static IApplicationBuilder EnableRequestBodyRewind(this IApplicationBuilder app)
{
if (app is null)
throw new ArgumentNullException(nameof(app));
return app.UseMiddleware<RequestBodyRewindMiddleware>();
}
}
我是这样注册的:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (!env.IsProduction())
app.EnableRequestBodyRewind();
}
最后是您要查找的代码:
public void ConfigureServices(IServiceCollection services)
{
services.PostConfigure<ApiBehaviorOptions>(opt =>
{
var defaultFactory = opt.InvalidModelStateResponseFactory;
opt.InvalidModelStateResponseFactory = context =>
{
AllowSynchronousIO(context.HttpContext);
var result = defaultFactory(context);
var bad = result as BadRequestObjectResult;
if (bad?.Value is ValidationProblemDetails problem)
LogInvalidModelState(context, problem);
return result;
static void AllowSynchronousIO(HttpContext httpContext)
{
IHttpBodyControlFeature? maybeSyncIoFeature = httpContext.Features.Get<IHttpBodyControlFeature>();
if (maybeSyncIoFeature is IHttpBodyControlFeature syncIoFeature)
syncIoFeature.AllowSynchronousIO = true;
}
static void LogInvalidModelState(ActionContext actionContext, ValidationProblemDetails error)
{
var errorJson = System.Text.Json.JsonSerializer.Serialize(error);
var reqBody = actionContext.HttpContext.Request.Body;
if (reqBody.CanSeek) reqBody.Position = 0;
var sr = new System.IO.StreamReader(reqBody);
string body = sr.ReadToEnd();
actionContext.HttpContext
.RequestServices.GetRequiredService<ILoggerFactory>()
.CreateLogger(nameof(ApiBehaviorOptions.InvalidModelStateResponseFactory))
.LogWarning("Invalid model state. Responded: '{ValidationProblemDetails}'. Received: '{Request}'", errorJson, body);
}
};
});
}
由于我们没有提供异步工厂功能的选项,并且默认情况下禁用同步读取,因此我们必须为此请求显式启用它。代码来自此处的公告问题。
确保在阅读正文流之前倒带它,并且不要丢弃它以防管道中的其他东西需要它。
在我的情况下,中间件未在生产中注册,CanSeek
将是false
并且StreamReader.ReadToEnd
只返回一个空字符串。
推荐阅读
- typescript - “ThunkAction”类型的参数不能分配给“AnyAction”类型的参数
- java - 如何在用 Java 开发的 Swing GUI 中从 python 程序输出中显示值
- java - 从带有对象的 ArrayList 中删除对象的问题
- ios - 根据可变数量的按钮自动调整水平 UIStackView 宽度?
- angular - createSelector - 我做错了
- android - 如何使用新的 Jetpack 首选项库实现首选项标头?
- ringcentral - RingCentral 与个人用户的事件订阅集成
- css - 边距 0 自动应用于父容器时视频不居中
- java - SectionsStatePagerAdapter 已弃用错误
- javascript - 将 GeoJSON 数据添加到本地托管的 Google Maps API