Cannot read Request.Content in ASP.NET WebApi cont

2020-03-01 03:11发布

问题:

I am writing a proxy using WebApi in a TransferMode.Streamed HttpSelfHostConfiguration exe.

When I use fiddler to post to my ApiController, for some reason I cannot read the Request.Content - it returns "" even if I have POSTed data

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)
    {
        ...
    }
}

Here is my web request

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"}

What I am doing wrong? Why is s.Result coming back as the empty string rather than the raw json?

回答1:

I too struggled with this. ReadAsStringAsync and ReadAsAsync return a task object. Referencing the Result property returns the content. It may be referencing the Result property causes the async read request to block.

Example:

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


回答2:

This signature for post eats the post data:

public HttpResponseMessage Post([FromBody]string postdata)

change it to:

public HttpResponseMessage Post()

then this call works fine to get the post data:

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

Tested it my self. use the first signature, str is empty, use the second str has post data!



回答3:

I realise this is old, and has been answered, but for what it's worth, the reason you can't use ReadAsStringAsync() isn't because it 'eats the data' as has been suggested, it's because the content is being processed as a stream and since the data has been consumed by the message formatter the Position of the stream is already at the end.

In order to use ReadAsStringAsync() you first need to reset the content stream Position to the beginning.

I do it like this: response.RequestMessage.Content.ReadAsStreamAsync().Result.Seek( 0, System.IO.SeekOrigin.Begin ) because I only have the HttpResponseMessage, but if you have direct access to the HttpRequestMessage (as you do inside the Controller) you can use Request.Content.ReadAsStreamAsync().Result.Seek( 0, System.IO.SeekOrigin.Begin ) which is functionally equivalent I suppose.

Late Edit

Reading async streams with Result as above will cause deadlocks and blocked threads under a number of circumstances. If you have to read from an async stream in a synchronous way, it's better to use the form:

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

where func is the async action you want to run, so in this case it would be something like async () => { await Request.Content.ReadAsStreamAsync(); } … in this way you can put the async parts of the method inside the StartNew part and properly unwrap any exceptions that happen when marshalling back to your synchronous code.

Better still, make the whole stack async.



回答4:

I believe you are right about the ApiController eating the Request.Content. The "Request" object that you see in the ApiController is actually of type System.Net.Http.HttpRequestMessage. I was able to work around this issue but backing up to the System.Web.HttpRequest object like such:

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

I don't know if the seek rewind is necessary but I put it in just in case.



回答5:

I got this working in the end by inheriting from the base interface instead of ApiController - I think the ApiController was modelbinding which was eating the response

edit: The right thing for building a proxy is a MessageHandler, not an ApiController



回答6:

  1. Request.Content.ReadAsStreamAsync().Result.Seek( 0, System.IO.SeekOrigin.Begin)
  2. new System.IO.StreamReader(Request.Content.ReadAsStreamAsync().Result).ReadToEnd()


回答7:

Try replacing ReadAsStringAsync() with ReadAsAsync<string>().



回答8:

You should use a complex type for your argument and then in the body use some json like

{ path: "c:..." }

Als use the

Content-Type: application/json; charset=UTF-8

header in your post request so that the web api knows that json is contained in the body



回答9:

This late addition to the answers here show how to read the POST data from 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();
    }
}