ASP.NET Core 3.1 Web API中“malformed JObject”错误排查与日志记录实现问题
ASP.NET Core 3.1 Web API中“malformed JObject”错误排查与日志记录实现问题
首先得给你点出核心问题:你当前写在Post方法里的日志代码,根本捕获不到"malformed JObject"这个错误。因为这个错误发生在Action方法执行之前的模型绑定阶段——当请求的JSON格式不符合JObject的解析要求时,ASP.NET Core的模型绑定器会直接返回400错误,根本不会进入你的Post方法,所以方法里的日志代码完全没机会执行。
下面给你一步步解决这个问题,同时修正现有代码的小瑕疵:
一、先修复现有日志代码的问题
你的日志写法存在两个小问题,先调整一下:
- 路径里的反斜杠要转义,要么用
@"D:\Logs\somefilename.txt"(逐字字符串),要么写成"D:\\Logs\\somefilename.txt",不然会被识别为转义字符导致路径错误。 - 手动调用
Close()和Dispose()没必要,用using语句会自动帮你释放资源,避免资源泄漏。
修正后的方法内日志代码:
public async Task<IActionResult> Post([FromBody] JObject transactionData) { // using语句自动管理StreamWriter生命周期 using var logWriter = new System.IO.StreamWriter(@"D:\Logs\somefilename.txt", true); try { logWriter.WriteLine($"{DateTime.Now.ToString("MM-dd-yyyy hh:mm:ss tt")}: {transactionData.ToString()}"); } catch (Exception e) { logWriter.WriteLine($"{DateTime.Now.ToString("MM-dd-yyyy hh:mm:ss tt")}: {e.Message}"); } // 无需手动Close/Dispose,using会自动处理 return Ok(); }
二、捕获模型绑定阶段的"malformed JObject"错误
要抓到这个阶段的错误,你需要在模型绑定完成前就记录请求的原始内容和错误信息,这里推荐两种实用方案:
方案1:用Action过滤器捕获绑定错误
创建一个自定义过滤器,在Action执行前检查ModelState是否有效,无效就记录原始请求体和错误:
public class LogInvalidModelStateFilter : ActionFilterAttribute { public override async void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { // 重置请求体流位置(因为可能已经被读取过) context.HttpContext.Request.Body.Position = 0; using var reader = new StreamReader(context.HttpContext.Request.Body); var rawRequestBody = await reader.ReadToEndAsync(); // 写入错误日志 using var logWriter = new StreamWriter(@"D:\Logs\model_binding_errors.txt", true); // 拼接所有绑定错误信息 var errorDetails = string.Join("; ", context.ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage)); logWriter.WriteLine($"{DateTime.Now.ToString("MM-dd-yyyy hh:mm:ss tt")}: 原始请求体: {rawRequestBody}"); logWriter.WriteLine($"{DateTime.Now.ToString("MM-dd-yyyy hh:mm:ss tt")}: 绑定错误: {errorDetails}"); logWriter.WriteLine("---"); } base.OnActionExecuting(context); } }
然后在Startup.cs的ConfigureServices方法里注册这个过滤器:
public void ConfigureServices(IServiceCollection services) { services.AddControllers(options => { // 添加自定义过滤器到全局 options.Filters.Add<LogInvalidModelStateFilter>(); }); }
方案2:用全局中间件捕获所有异常请求
如果你想记录所有请求的原始内容(不管是否绑定失败),可以写一个中间件:
public class RequestLoggingMiddleware { private readonly RequestDelegate _next; public RequestLoggingMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // 保存原始请求体流(因为流只能读取一次) var originalBody = context.Request.Body; try { using var memoryStream = new MemoryStream(); await context.Request.Body.CopyToAsync(memoryStream); memoryStream.Position = 0; // 读取原始请求体 using var reader = new StreamReader(memoryStream); var rawRequestBody = await reader.ReadToEndAsync(); // 重置流位置,让后续中间件能正常读取 memoryStream.Position = 0; context.Request.Body = memoryStream; // 继续处理请求 await _next(context); // 如果返回400错误(通常是模型绑定失败),记录日志 if (context.Response.StatusCode == 400) { using var logWriter = new StreamWriter(@"D:\Logs\bad_requests.txt", true); logWriter.WriteLine($"{DateTime.Now.ToString("MM-dd-yyyy hh:mm:ss tt")}: 请求路径: {context.Request.Path}"); logWriter.WriteLine($"{DateTime.Now.ToString("MM-dd-yyyy hh:mm:ss tt")}: 请求体: {rawRequestBody}"); logWriter.WriteLine("---"); } } finally { // 恢复原始请求体流 context.Request.Body = originalBody; } } }
在Startup.cs的Configure方法里注册中间件(注意顺序要放在路由之前):
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ...其他中间件(比如UseHttpsRedirection) app.UseMiddleware<RequestLoggingMiddleware>(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
额外小建议
- 尽量不要手动用
StreamWriter写日志,ASP.NET Core内置的ILogger系统更专业,支持文件、控制台、ELK等多种日志输出方式,还能自动管理日志级别和滚动策略。你可以直接在控制器里注入ILogger<YourController>来替代手动写文件。 - 如果业务允许,建议用强类型模型接收请求(比如定义一个
TransactionData类),这样模型绑定的错误信息会更具体,排查问题也更方便。
备注:内容来源于stack exchange,提问作者SoftwareDveloper
相关产品推荐
相关产品推荐

