.NET Core:当 ApiController 缩短管道时访问错误请求的请求数据

.NET Core: access request data on bad request when ApiController shortcuts the pipeline

我在 Github 上创建了一个小型 public 游乐场,以应对所有遇到日志记录问题的人。欢迎加入。


我仍在尝试针对我的请求使用一击即中的所有日志记录过滤器。

目标很简单。与其在每个控制器方法中编写日志,不如在过滤器(或中间件)中实现一次,然后将它们全局应用于所有方法。当出现问题时使用它我可以看到使用了哪些数据导致了问题。

例如控制器操作(使用全局应用过滤器)

[HttpPut("{id:int}")
public IActionResult UpdateModel(int id, [FromBody] MyRequestModel request) {}

会创建类似

的日志
[Timestamp] INFO: MyNamespace.Controllers.MyModelController.UpdateModel
{ 
  "Parameters": {
     "Id": 1,
     "Request": {
       "Name": "abc",
       "SomeInt": 3
     }
  }
}

很好,不再需要在每个方法中手动记录。

但是等等:它是一个 API 并且我使用了 [ApiController] 属性并且请求模型具有无效数据(假设 Name = "a" 但它至少需要长度为3).

这给了我 3 个问题

  1. ActionFilter.OnActionExecuting 是快捷方式,不会被调用(由于 ApiController)
  2. 似乎跳过了参数的绑定,并且绑定的(无效的)数据未应用于 ActionArguments
  3. 只调用了 ResultFilter.OnResultExecuted 但似乎没有办法 accessing/logging 无效数据

这在某种程度上意味着日志记录仅在一切顺利时才有效,但最有趣的日志不是那些出问题的日志吗?

public void OnActionExecuting(ActionExecutingContext context)
  {
    _loggerFactory
    .CreateLogger(context.ActionDescriptor.DisplayName.Split(" ")[0])
    .LogInformation
    (
      JsonConvert.SerializeObject
      (
        new { Parameters = context.ActionArguments },
        Formatting.Indented
      )
    );
  }

要点 1:我当然可以从每个控制器中删除 ApiController 并手动返回 return BadRequest 结果。但我喜欢集中式的方式,并愿意坚持下去。

我喜欢模型绑定方法让我 类 序列化日志(而不是将请求正文作为一个字符串手动读取)。使用自定义 Json 合同解析器,我能够将模型属性标记为敏感,并且它们隐藏在日志中(对于那些非常关心安全性的人)。

所以我的实际问题是:

有没有办法在 ResultFilter 中获取模型绑定值,或者它们是否被完全丢弃? 或者有没有办法挂钩模型 binding/model 验证并在日志被丢弃之前将其写入那里? 两者都特别适用于 ApiController 属性开始缩短过滤器管道的情况。


public class LogFilter : IActionFilter, IResultFilter, IExceptionFilter
{

    private readonly ILoggerFactory _loggerFactory;

    public LogFilter(ILoggerFactory loggerFactory)
    {
      _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
      _loggerFactory
         .CreateLogger(context.ActionDescriptor.DisplayName.Split(" ")[0])
         .LogInformation
         (
           JsonConvert.SerializeObject
           (
             new
             {
               RequestBody = ReadBodyAsString(context.HttpContext.Request),
               Parameter = context.ActionArguments
             },
             Formatting.Indented
           )
         );
    }

    public void OnActionExecuted(ActionExecutedContext context) {}      

    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
          _loggerFactory
            .CreateLogger(context.ActionDescriptor.DisplayName.Split(" ")[0])
            .LogWarning
            (
               JsonConvert.SerializeObject
               (
                 new
                 {
                   RequestBody = ReadBodyAsString(context.HttpContext.Request),
                   ModelErrors = context.ModelState
                    .Where(kvp => kvp.Value.Errors.Count > 0)
                    .ToDictionary
                    (
                      kvp => kvp.Key,
                      kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
                    )
                 },
                 Formatting.Indented
               )
            );
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) {}

    public void OnException(ExceptionContext context)
    {

        _loggerFactory
        .CreateLogger(context.ActionDescriptor.DisplayName.Split(" ")[0])
        .LogError
        (
          context.Exception,
          JsonConvert.SerializeObject
          (
            new
            {
              //RequestBody = ReadBodyAsString(context.HttpContext.Request) // body is disposed here
            },
            Formatting.Indented
          )
        );

    }
}

您可以创建一个 class 来实现 IActionFilter 的 OnActionExecuting 方法。并将其注册到全局过滤器中,以便它适用于所有控制器和操作。当模型绑定发生异常时(长度的原因),即使使用了 [ApiController],您的 OnActionExecuting 方法仍然会被调用,您可以在那里记录它。

例如

public class MyActionFilterAttribute: IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        //Your logic to log the request, you can get the details from
        //context parameter

        //You can check if model state is valid also by using the property
        //context.ModelState.IsValid
    }
}

在Startup.cs中,您需要将SuppressModelStateInvalidFilter设置为true。这不会自动 return 400 状态。您的控制器方法仍会被调用,并且由于您有操作过滤器,因此会调用 OnActionExecuting。

public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc(
            config =>
            {
                config.Filters.Add(new MyActionFilterAttribute());
            })
            .ConfigureApiBehaviorOptions(options =>
            {
                options.SuppressModelStateInvalidFilter = true;
            });
    }

关于 ActionFilter 只捕获成功的请求,我认为你是对的。 对于您无效的请求,也许您可​​以看看InvalidModelStateResponseFactory

使用 options.InvalidModelStateResponseFactory 时,您可以阅读模型和验证错误。