.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 个问题
- ActionFilter.OnActionExecuting 是快捷方式,不会被调用(由于 ApiController)
- 似乎跳过了参数的绑定,并且绑定的(无效的)数据未应用于 ActionArguments
- 只调用了 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 时,您可以阅读模型和验证错误。
我在 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 个问题
- ActionFilter.OnActionExecuting 是快捷方式,不会被调用(由于 ApiController)
- 似乎跳过了参数的绑定,并且绑定的(无效的)数据未应用于 ActionArguments
- 只调用了 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 时,您可以阅读模型和验证错误。