Це трохи стара тема, але, оскільки я сюди потрапив, я вирішив опублікувати свої висновки, щоб вони могли допомогти іншим.
По-перше, у мене була та сама проблема, де я хотів отримати Request.Body і зробити щось із цим (реєстрація / аудит). Але в іншому випадку я хотів, щоб кінцева точка виглядала однаково.
Отже, здавалося, що виклик EnableBuffering () може зробити трюк. Потім ви можете здійснити пошук (0, ххх) на тілі і перечитати вміст тощо.
Однак це призвело до мого наступного випуску. Я отримував би винятки "Синхронні операції заборонені" при доступі до кінцевої точки. Отже, обхідне рішення полягає у встановленні властивості AllowSynchronousIO = true у параметрах. Існує ряд способів досягти цього (але тут не важливо деталізувати ..)
ПОТІМ, наступне питання полягає в тому, що коли я йду читати Запит, він уже вирішений. Тьфу. Отже, що дає?
Я використовую Newtonsoft.JSON як мій парсер [FromBody] у виклику endpiont. Це те, що відповідає за синхронне читання, а також закриває потік, коли це робиться. Рішення? Прочитайте потік, перш ніж він потрапить до аналізу 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);
}
}
}
}
Тож тепер я можу отримати доступ до тіла за допомогою HttpContext.Items ["request_body"] у кінцевих точках, які мають атрибут [ReadRequestBodyIntoItems].
Але людино, це здається занадто багато обручів, щоб проскочити. Отже, ось де я закінчив, і я справді цим задоволений.
Моя кінцева точка починалася приблизно так:
[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 робить цю магію для мене, але тут я не витрачаю час на читання потоку двічі (можливо, буферизація кожного разу), і код досить чіткий і чистий.
Якщо вам потрібна ця функціональність на багатьох кінцевих точках, можливо, підходи до проміжного програмного забезпечення можуть бути чистішими, або ви можете принаймні інкапсулювати витяг тіла у функцію розширення, щоб зробити код більш стислим.
У будь-якому випадку, я не знайшов жодного джерела, яке торкалося б усіх 3 аспектів цієї проблеми, отже, і цієї публікації. Сподіваємось, це комусь допомагає!
До речі: це використовувало ASP .NET Core 3.1.