You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.21 12:57:59