Este é um tópico um pouco antigo, mas desde que cheguei aqui, decidi postar minhas descobertas para que possam ajudar outras pessoas.
Primeiro, eu tive o mesmo problema, onde eu queria pegar o Request.Body e fazer algo com ele (registro / auditoria). Mas, caso contrário, eu queria que o ponto final fosse o mesmo.
Portanto, parecia que a chamada EnableBuffering () poderia resolver o problema. Então você pode fazer uma busca (0, xxx) no corpo e reler o conteúdo, etc.
No entanto, isso levou ao meu próximo problema. Eu obteria exceções "Operações síncronas não são permitidas" ao acessar o ponto de extremidade. Portanto, a solução alternativa é definir a propriedade AllowSynchronousIO = true, nas opções. Existem várias maneiras de fazer isso (mas não é importante detalhar aqui ..)
ENTÃO, o próximo problema é que quando vou ler o Request.Body ele já foi descartado. Ugh. Então, o que aconteceu?
Estou usando o Newtonsoft.JSON como meu analisador [FromBody] na chamada do endpiont. Isso é o que é responsável pelas leituras síncronas e também fecha o fluxo quando é concluído. Solução? Leia o fluxo antes de chegar à análise JSON? Claro, isso funciona e acabei com o seguinte:
/// <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);
}
}
}
}
Agora, posso acessar o corpo usando HttpContext.Items ["request_body"] nos pontos de extremidade que têm o atributo [ReadRequestBodyIntoItems].
Mas cara, isso parece muitos obstáculos para pular. Então foi aqui que eu terminei e estou muito feliz com isso.
Meu endpoint começou como algo como:
[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...
}
Mas é muito mais simples apenas alterar a assinatura, assim:
[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...
}
}
Eu realmente gostei disso porque ele lê o fluxo do corpo apenas uma vez e eu tenho o controle da desserialização. Claro, é bom se o ASP.NET core fizer essa mágica para mim, mas aqui não perco tempo lendo o fluxo duas vezes (talvez armazenando em buffer a cada vez) e o código é bastante claro e limpo.
Se você precisar dessa funcionalidade em muitos terminais, talvez as abordagens de middleware possam ser mais limpas ou você pode pelo menos encapsular a extração do corpo em uma função de extensão para tornar o código mais conciso.
De qualquer forma, não encontrei nenhuma fonte que abordasse todos os 3 aspectos desse assunto, daí este post. Espero que isso ajude alguém!
BTW: isso estava usando ASP .NET Core 3.1.