Подтвердить что ты не робот

Обратный поток тела запроса назад

Я повторно использую регистратор запросов как Owin Middleware, который регистрирует URL-адрес запроса и тело всех входящих запросов. Я могу прочитать тело, но если я делаю параметр body в моем контроллере, то он равен нулю.

Я предполагаю, что это null, потому что позиция потока находится в конце, поэтому нет ничего, что можно было бы прочитать, когда он пытается десериализовать тело. У меня была аналогичная проблема в предыдущей версии Web API, но я смог установить позицию Stream обратно на 0. Этот конкретный поток генерирует исключение This stream does not support seek operations.

В самой последней версии Web API 2.0 я мог бы вызвать Request.HttpContent.ReadAsStringAsync() внутри моего регистратора запросов, и тело по-прежнему будет поступать на контроллер в такт.

Как перемотать поток после его чтения?

или

Как я могу прочитать тело запроса без его использования?

public class RequestLoggerMiddleware : OwinMiddleware
{
    public RequestLoggerMiddleware(OwinMiddleware next)
        : base(next)
    {
    }

    public override Task Invoke(IOwinContext context)
    {
        return Task.Run(() => {
            string body = new StreamReader(context.Request.Body).ReadToEnd();
            // log body

            context.Request.Body.Position = 0; // cannot set stream position back to 0
            Console.WriteLine(context.Request.Body.CanSeek); // prints false
            this.Next.Invoke(context);
        });
    }
}

public class SampleController : ApiController 
{
    public void Post(ModelClass body)
    {
        // body is now null if the middleware reads it
    }
}
4b9b3361

Ответ 1

Просто найдено одно решение. Замена исходного потока новым потоком, содержащим данные.

    public override Task Invoke(IOwinContext context)
    {
        return Task.Run(() => {
            string body = new StreamReader(context.Request.Body).ReadToEnd();
            // log body

            byte[] requestData = Encoding.UTF8.GetBytes(body);
            context.Request.Body = new MemoryStream(requestData);
            this.Next.Invoke(context);
        });
    }

Если вы имеете дело с большими объемами данных, я уверен, что FileStream также будет работать как замена.

Ответ 2

Здесь небольшое улучшение в первом ответе Despertar, которое мне очень помогло, но я столкнулся с проблемой при работе с двоичными данными. Промежуточный шаг извлечения потока в строку и затем вернув его в массив байтов, используя Encoding.UTF8.GetBytes(body), помешает двоичный контент (содержимое изменится, если оно не закодировано в кодировке UTF8). Здесь мое исправление с использованием Stream.CopyTo():

public override Task Invoke(IOwinContext context)
{
    return Task.Run(() => 
    {
        using (var streamCopy = new MemoryStream())
        {
            context.Request.Body.CopyTo(streamCopy);
            streamCopy.Position = 0; // rewind

            string body = new StreamReader(streamCopy).ReadToEnd();
            // log body

            streamCopy.Position = 0; // rewind again
            context.Request.Body = streamCopy; // put back in place for downstream handlers

            this.Next.Invoke(context);
        }
    });
}

Кроме того, MemoryStream хорош, потому что вы можете проверить длину потока перед записью всего тела (что я не хочу делать, если кто-то загружает огромный файл).

Ответ 3

Я знаю, что это старо, но только для того, чтобы помочь любому, кто сталкивается с этим. Вам нужно искать в потоке: context.Request.Body.Seek(0, SeekOrigin.Begin);