Questo è un po 'un vecchio thread, ma da quando sono arrivato qui, ho pensato di pubblicare le mie scoperte in modo che potessero aiutare gli altri.
Innanzitutto, ho avuto lo stesso problema, in cui volevo ottenere la richiesta.Body e fare qualcosa con quello (registrazione / auditing). Ma per il resto volevo che l'endpoint avesse lo stesso aspetto.
Quindi, sembrava che la chiamata EnableBuffering () potesse fare il trucco. Quindi puoi fare una ricerca (0, xxx) sul corpo e rileggere i contenuti, ecc.
Tuttavia, questo ha portato al mio prossimo numero. Ricevo eccezioni "Le operazioni sincrone non sono consentite" quando si accede all'endpoint. Quindi, la soluzione alternativa è impostare la proprietà AllowSynchronousIO = true, nelle opzioni. Ci sono diversi modi per farlo (ma non è importante per i dettagli qui ..)
ALLORA, il prossimo numero è che quando vado a leggere la richiesta, il corpo è già stato eliminato. Uffa. Allora, cosa succede?
Sto usando Newtonsoft.JSON come parser [FromBody] nella chiamata endpiont. Questo è ciò che è responsabile delle letture sincrone e chiude anche il flusso quando ha finito. Soluzione? Leggere il flusso prima che arrivi all'analisi JSON? Certo, funziona e ho finito con questo:
/// <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);
}
}
}
}
Quindi ora posso accedere al corpo utilizzando HttpContext.Items ["request_body"] negli endpoint che hanno l'attributo [ReadRequestBodyIntoItems].
Ma amico, sembra che ci siano troppi cerchi da superare. Quindi ecco dove sono finito e ne sono davvero felice.
Il mio endpoint è iniziato come qualcosa di simile:
[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...
}
Ma è molto più semplice cambiare la firma, in questo modo:
[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...
}
}
Mi è piaciuto molto perché legge il flusso del corpo solo una volta e ho il controllo della deserializzazione. Certo, è bello se ASP.NET core fa questa magia per me, ma qui non perdo tempo a leggere lo stream due volte (forse buffering ogni volta), e il codice è abbastanza chiaro e pulito.
Se hai bisogno di questa funzionalità su molti endpoint, forse gli approcci middleware potrebbero essere più puliti, oppure puoi almeno incapsulare l'estrazione del corpo in una funzione di estensione per rendere il codice più conciso.
Ad ogni modo, non ho trovato alcuna fonte che abbia toccato tutti e 3 gli aspetti di questo problema, da qui questo post. Spero che questo aiuti qualcuno!
BTW: questo utilizzava ASP .NET Core 3.1.