Не удается прочитать Request.Content в контроллере ASP.NET WebApi

Я пишу прокси, используя WebApi в exe-файле TransferMode.Streamed HttpSelfHostConfiguration.

Когда я использую fiddler для публикации в моем ApiController, по какой-то причине я не могу прочитать Request.Content - он возвращает "", даже если у меня есть данные POST

public class ApiProxyController : ApiController
{

    public Task<HttpResponseMessage> Post(string path)
    {
        return Request.Content.ReadAsStringAsync().ContinueWith(s =>
        {
            var content = new StringContent(s.Result); //s.Result is ""
                CopyHeaders(Request.Content.Headers, content.Headers);
            return Proxy(path, content);
        }).Unwrap();
    }

    private Task<HttpResponseMessage> Proxy(string path, HttpContent content)
    {
        ...
    }
}

Вот мой веб-запрос

POST http://localhost:3001/api/values HTTP/1.1
Host: localhost:3001
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Content-Type: application/json
Content-Length: 26

{ "text":"dfsadfsadfsadf"}

Что я делаю неправильно? Почему s.Result возвращается как пустая строка, а не как необработанный json?


person mcintyre321    schedule 12.04.2012    source источник


Ответы (10)


Я тоже боролся с этим. ReadAsStringAsync и ReadAsAsync возвращают объект задачи. Ссылка на свойство Result возвращает содержимое. Возможно, ссылка на свойство Result приводит к блокировке запроса на асинхронное чтение.

Пример:

string str = response.Content.ReadAsStringAsync().Result;
person JeffR    schedule 05.09.2012
comment
используйте ожидание вместо .Reseult - person victor; 19.10.2017
comment
Это будет работать только в webapi, если вы сбросите позицию потока. См. комментарий ниже, сделанный Ричардом - person mike gold; 06.07.2018

Я понимаю, что это устарело, и на него был дан ответ, но для чего это стоит, причина, по которой вы не можете использовать ReadAsStringAsync(), заключается не в том, что он «съедает данные», как было предложено, а в том, что контент обрабатывается как поток и поскольку данные были использованы средством форматирования сообщений, позиция потока уже находится в конце.

Чтобы использовать ReadAsStringAsync(), сначала необходимо сбросить позицию потока контента в начало.

Я делаю это так: response.RequestMessage.Content.ReadAsStreamAsync().Result.Seek( 0, System.IO.SeekOrigin.Begin ), потому что у меня есть только HttpResponseMessage, но если у вас есть прямой доступ к HttpRequestMessage (как вы делаете это внутри контроллера), вы можете использовать Request.Content.ReadAsStreamAsync().Result.Seek( 0, System.IO.SeekOrigin.Begin ), который функционально эквивалентен, я полагаю.

Позднее редактирование

Чтение асинхронных потоков с Result, как указано выше, приведет к взаимоблокировкам и блокировке потоков при ряде обстоятельств. Если вам нужно читать из асинхронного потока синхронно, лучше использовать форму:

 new TaskFactory( CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default )
      .StartNew<Task<TResult>>( func )
      .Unwrap<TResult>()
      .GetAwaiter()
      .GetResult();

где func — это асинхронное действие, которое вы хотите запустить, поэтому в данном случае это будет что-то вроде async () => { await Request.Content.ReadAsStreamAsync(); }… таким образом вы можете поместить асинхронные части метода внутрь части StartNew и должным образом развернуть любые исключения, возникающие при сортировке обратно к вашему синхронный код.

А еще лучше сделать весь стек асинхронным.

person Richard Hauer    schedule 14.01.2016
comment
Я считаю, что если вы работаете в режиме потокового запроса (что важно при написании прокси и вы не хотите потреблять много памяти), вы не можете сбросить местоположение потока. Кроме того, так ли ужасно использовать «съеденный» вместо «потребленный»? Сомневаюсь, что кто-то думал, что происходит что-то биологическое. :) - person mcintyre321; 15.01.2016
comment
@mcintyre321 Лол. Хорошо, «потребляться» хочу, такой отличный выбор слов. Я думаю, что «съеденный» подразумевает для меня, что вы не можете вернуть его. FWIW Мне удалось сбросить местоположение потока l (которое буферизовано, поэтому не читается напрямую из удаленного потока). К вашему сведению, интересно, что произойдет с очень большим потоком запросов, где данные превышают размер буфера? У меня была проблема с повторным чтением составного потока, поскольку он продолжал удалять себя. В итоге сначала прочитал его в поток памяти, а затем разобрал на ReadAsMultipartAsnc() - person Richard Hauer; 16.01.2016
comment
@RichardHauer: по какой-то причине для меня возвращается целое число вместо строки. - person micahhoover; 30.03.2019
comment
@micahhoover Команда Seek() возвращает int (новая позиция в потоке), конечно, но вам, вероятно, это значение не нужно. После Seeking вы можете прочитать содержимое потока, используя ReadAsStreamAsync(). - person Richard Hauer; 31.03.2019
comment
@RichardHauer: спасибо. Оказывается, мой [FromBody] перебирал GetNext. Я просто бросил это, вытащил вручную, и все заработало. - person micahhoover; 01.04.2019

Эта подпись для поста съедает данные поста:

public HttpResponseMessage Post([FromBody]string postdata)

изменить его на:

public HttpResponseMessage Post()

то этот вызов отлично работает для получения данных сообщения:

string str = response.Content.ReadAsStringAsync().Result;

Протестировал на себе. используйте первую подпись, строка пуста, используйте вторую строку с почтовыми данными!

person JJ_Coder4Hire    schedule 27.06.2014
comment
но тогда как вы можете получить постданные в методе? - person Hoàng Long; 18.11.2015
comment
var request = await Request.Content.ReadAsStringAsync(); - person War; 03.12.2015
comment
Это также относится к контроллерам в MVC. Использование FormCollection form съест содержимое запроса. - person jahu; 11.04.2016
comment
Вы только что сэкономили мне бесчисленные часы отладки. - person Himanshu Patel; 25.05.2017
comment
Асинхронный способ действительно будет общедоступным асинхронным Task‹HttpResponseMessage› Post() И затем string str = await response.Content.ReadAsStringAsync(); - person Christopher Bonitz; 27.09.2017

Я считаю, что вы правы в том, что ApiController ест Request.Content. Объект «Запрос», который вы видите в ApiController, на самом деле имеет тип System.Net.Http.HttpRequestMessage. Мне удалось обойти эту проблему, но выполнить резервное копирование объекта System.Web.HttpRequest следующим образом:

Dim content as string
If HttpContext.Current.Request.InputStream.CanSeek Then
    HttpContext.Current.Request.InputStream.Seek(0, IO.SeekOrigin.Begin)
End If
Using reader As New System.IO.StreamReader(HttpContext.Current.Request.InputStream)
    content = reader.ReadToEnd()
End Using

Не знаю, нужна ли перемотка поиска, но на всякий случай поставил.

person EverPresent    schedule 08.05.2013
comment
Оказалось, что ApiController не подходит для создания прокси. В конце концов я использовал MessageHandler, и все было хорошо. - person mcintyre321; 09.05.2013
comment
Это прекрасно работает. Мне это нужно было только для отладки, так что это было здорово. - person BradLaney; 13.05.2013

В конце концов, я получил эту работу, унаследовав от базового интерфейса вместо ApiController - я думаю, что ApiController был привязкой модели, которая ела ответ

изменить: правильная вещь для создания прокси-сервера - это MessageHandler, а не ApiController

person mcintyre321    schedule 16.04.2012
comment
MSDN указывает, что MessageHandlers следует использовать для сквозных задач. Для построения прокси должны нормально работать стандартные контроллеры MVC. - person arviman; 23.06.2014
comment
Нет, если вы создаете потоковый прокси. - person mcintyre321; 23.06.2014
comment
Я имею в виду, что вы можете использовать идею блокировки, пока не получите результат, как предлагает JeffR, прежде чем перенаправить на прокси. (Сейчас я создаю что-то подобное, и оно отлично работает). - person arviman; 23.06.2014

  1. Request.Content.ReadAsStreamAsync().Result.Seek( 0, System.IO.SeekOrigin.Begin)
  2. новый System.IO.StreamReader(Request.Content.ReadAsStreamAsync().Result).ReadToEnd()
person HGMamaci    schedule 16.11.2017

Это последнее дополнение к ответам здесь показывает, как читать данные POST из WebAPI:

string postData;
using (var stream = await request.Content.ReadAsStreamAsync())
{
    stream.Seek(0, SeekOrigin.Begin);
    using (var sr = new StreamReader(stream))
    {
        postData = await sr.ReadToEndAsync();
    }
}
person Soma Mbadiwe    schedule 31.12.2019

Попробуйте заменить ReadAsStringAsync() на ReadAsAsync<string>().

person David Peden    schedule 16.04.2012

Вы должны использовать сложный тип для своего аргумента, а затем в теле использовать какой-то json, например

{ путь: "с:..." }

Также используйте

Тип содержимого: приложение/json; кодировка = UTF-8

заголовок в вашем почтовом запросе, чтобы веб-API знал, что json содержится в теле

person Gertjan    schedule 30.11.2012

Попробуйте использовать CopyToAsync вместо ReadAsStringAsync, похоже, проблема решена.

var ms = new MemoryStream();
await response.Content.CopyToAsync(ms);
ms.Seek(0, SeekOrigin.Begin);

var sr = new StreamReader(ms);
responseContent = sr.ReadToEnd();
person mltr26    schedule 27.04.2021