这是一个有点老的帖子,但自从我来到这里,我想我会发布我的发现,以便他们可以帮助其他人。
首先,我遇到了同样的问题,我想获取 Request.Body 并对其进行处理(日志记录/审计)。但除此之外,我希望端点看起来相同。
所以,EnableBuffering() 调用似乎可以解决问题。然后你可以在 body 上做一个 Seek(0,xxx) 并重新读取内容等。
然而,这导致了我的下一个问题。访问端点时,我会收到“不允许同步操作”异常。因此,解决方法是在选项中设置属性 AllowSynchronousIO = true。有很多方法可以做到这一点(但这里不重要..)
那么,下一个问题是,当我阅读 Request.Body 时,它已经被处理掉了。啊。那么,什么给了?
我在 endpiont 调用中使用 Newtonsoft.JSON 作为我的 [FromBody] 解析器。这就是同步读取的原因,它在完成时也会关闭流。解决方案?在进行 JSON 解析之前读取流?当然,这行得通,我最终得到了这个:
/// <summary>
/// quick and dirty middleware that enables buffering the request body
/// </summary>
/// <remarks>
/// this allows us to re-read the request body's inputstream so that we can capture the original request as is
/// </remarks>
public class ReadRequestBodyIntoItemsAttribute : AuthorizeAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (context == null) return;
// NEW! enable sync IO beacuse the JSON reader apparently doesn't use async and it throws an exception otherwise
var syncIOFeature = context.HttpContext.Features.Get<IHttpBodyControlFeature>();
if (syncIOFeature != null)
{
syncIOFeature.AllowSynchronousIO = true;
var req = context.HttpContext.Request;
req.EnableBuffering();
// read the body here as a workarond for the JSON parser disposing the stream
if (req.Body.CanSeek)
{
req.Body.Seek(0, SeekOrigin.Begin);
// if body (stream) can seek, we can read the body to a string for logging purposes
using (var reader = new StreamReader(
req.Body,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: false,
bufferSize: 8192,
leaveOpen: true))
{
var jsonString = reader.ReadToEnd();
// store into the HTTP context Items["request_body"]
context.HttpContext.Items.Add("request_body", jsonString);
}
// go back to beginning so json reader get's the whole thing
req.Body.Seek(0, SeekOrigin.Begin);
}
}
}
}
所以现在,我可以在具有 [ReadRequestBodyIntoItems] 属性的端点中使用 HttpContext.Items["request_body"] 访问正文。
但是,伙计,这似乎是太多的箍要跳过。所以这就是我结束的地方,我真的很高兴。
我的端点开始类似于:
[HttpPost("")]
[ReadRequestBodyIntoItems]
[Consumes("application/json")]
public async Task<IActionResult> ReceiveSomeData([FromBody] MyJsonObjectType value)
{
val bodyString = HttpContext.Items["request_body"];
// use the body, process the stuff...
}
但是更改签名要简单得多,如下所示:
[HttpPost("")]
[Consumes("application/json")]
public async Task<IActionResult> ReceiveSomeData()
{
using (var reader = new StreamReader(
Request.Body,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: false
))
{
var bodyString = await reader.ReadToEndAsync();
var value = JsonConvert.DeserializeObject<MyJsonObjectType>(bodyString);
// use the body, process the stuff...
}
}
我真的很喜欢这个,因为它只读取一次正文流,并且我可以控制反序列化。当然,如果 ASP.NET core 能为我做到这一点,那就太好了,但是在这里我不会浪费时间读取两次流(可能每次都缓冲),并且代码非常清晰。
如果您在很多端点上都需要此功能,也许中间件方法可能更简洁,或者您至少可以将主体提取封装到扩展函数中以使代码更简洁。
无论如何,我没有找到任何涉及这个问题的所有 3 个方面的来源,因此这篇文章。希望这可以帮助某人!
顺便说一句:这是使用 ASP .NET Core 3.1。