【问题标题】:Cannot read Request.Content in ASP.NET WebApi controller无法读取 ASP.NET WebApi 控制器中的 Request.Content
【发布时间】:2012-04-12 16:32:20
【问题描述】:

我正在 TransferMode.Streamed HttpSelfHostConfiguration exe 中使用 WebApi 编写代理。

当我使用提琴手发布到我的 ApiController 时,由于某种原因我无法读取 Request.Content - 即使我已经发布了数据,它也会返回“”

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 形式返回?

【问题讨论】:

    标签: c# asynchronous asp.net-web-api


    【解决方案1】:

    我也为此苦苦挣扎。 ReadAsStringAsyncReadAsAsync 返回一个任务对象。引用 Result 属性会返回内容。它可能是引用 Result 属性导致异步读取请求阻塞。

    例子:

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

    【讨论】:

    • 使用 await 代替 .Reseult
    • 这仅在您重置流位置时在 webapi 中有效。请参阅下面由 Richard 发表的评论
    【解决方案2】:

    我意识到这是旧的,并且已经得到了回答,但是对于它的价值,你不能使用 ReadAsStringAsync() 的原因并不是因为它像建议的那样“吃掉数据”,而是因为内容是正在作为流处理,并且由于数据已被消息格式化程序使用,因此流的位置已经在末尾。

    要使用ReadAsStringAsync(),您首先需要将内容流位置重置为开头。

    我这样做:response.RequestMessage.Content.ReadAsStreamAsync().Result.Seek( 0, System.IO.SeekOrigin.Begin ) 因为我只有 HttpResponseMessage,但如果您可以直接访问 HttpRequestMessage(就像您在 Controller 中所做的那样),您可以使用 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 () =&gt; { await Request.Content.ReadAsStreamAsync(); } ... 这样您就可以将方法的异步部分放入StartNew 部分并正确展开编组回同步代码时发生的任何异常。

    更好的是,让整个堆栈异步。

    【讨论】:

    • 我相信,如果您使用流式请求模式(这对于编写代理很重要并且您不想消耗大量内存),则无法重置流位置。另外,用“吃”代替“吃”有那么可怕吗?我怀疑有人认为生物发生了什么事。 :)
    • @mcintyre321 哈哈。 OK,'消费'想要这么棒的词选才对。我想“吃掉”对我来说意味着你不能把它拿回来。 FWIW 我能够重置流 l 位置(已缓冲,因此不能直接从远程流中读取)。就您而言,我想知道如果数据超过缓冲区大小的非常大的请求流会发生什么?我在重新读取多部分流时确实遇到了问题,因为它一直在自行处理。最后先将其读入内存流,然后将其解析为ReadAsMultipartAsnc()
    • @RichardHauer:出于某种原因,它为我返回一个 int 而不是一个字符串。
    • @micahhoover Seek() 命令返回int(流中的新位置),当然,但您可能不需要该值。在Seeking 之后,您可以使用ReadAsStreamAsync() 读取流内容。
    • @RichardHauer:谢谢。事实证明我的 [FromBody] 正在遍历 GetNext。我刚刚放下它,手动拉它,一切正常。
    【解决方案3】:

    这个帖子的签名吃掉了帖子数据:

    public HttpResponseMessage Post([FromBody]string postdata)
    

    改成:

    public HttpResponseMessage Post()
    

    然后这个调用可以很好地获取帖子数据:

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

    我自己测试过。使用第一个签名,str为空,使用第二个str有post数据!

    【讨论】:

    • 但是如何获取方法中的postdata呢?
    • var request = await Request.Content.ReadAsStringAsync();
    • 这也适用于 MVC 中的控制器。使用FormCollection form 会吃掉请求的内容。
    • 你为我节省了无数小时的调试时间。
    • 异步方式真的是 public async Task Post() 然后 string str = await response.Content.ReadAsStringAsync();
    【解决方案4】:

    我相信您对 ApiController 吃掉 Request.Content 的看法是正确的。您在 ApiController 中看到的“Request”对象实际上是 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

    我不知道是否需要回溯,但我把它放在以防万一。

    【讨论】:

    • 事实证明,ApiController 对于构建代理来说是错误的。最后我使用了 MessageHandler,一切都很好。
    • 这很好用。我只需要它来进行调试,所以这太棒了。
    【解决方案5】:

    我最终通过从基本接口而不是 ApiController 继承来完成这项工作 - 我认为 ApiController 是模型绑定,它正在吃掉响应

    编辑:构建代理的正确方法是 MessageHandler,而不是 ApiController

    【讨论】:

    • MSDN 指出 MessageHandlers 应该用于横切关注点。对于构建代理,标准 MVC 控制器应该可以正常工作。
    • 如果您正在构建流代理,则不会。
    • 我的意思是,你可以使用阻塞的想法,直到你得到 JeffR 建议的结果,然后再转发到代理。 (我现在正在构建类似的东西,而且效果很好)。
    【解决方案6】:
    1. Request.Content.ReadAsStreamAsync().Result.Seek(0, System.IO.SeekOrigin.Begin)
    2. 新 System.IO.StreamReader(Request.Content.ReadAsStreamAsync().Result).ReadToEnd()

    【讨论】:

      【解决方案7】:

      此处对答案的最新补充显示了如何从 WebAPI 读取 POST 数据:

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

      【讨论】:

        【解决方案8】:

        尝试将ReadAsStringAsync() 替换为ReadAsAsync&lt;string&gt;()

        【讨论】:

          【解决方案9】:

          你应该为你的论点使用一个复杂的类型,然后在正文中使用一些类似的 json

          { 路径:“c:...” }

          Als 使用

          内容类型:应用程序/json;字符集=UTF-8

          您的 post 请求中的 header,以便 web api 知道 json 包含在 body 中

          【讨论】:

            【解决方案10】:

            尝试使用 CopyToAsync 而不是 ReadAsStringAsync 似乎可以解决问题

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

            【讨论】:

              猜你喜欢
              • 2012-09-11
              • 1970-01-01
              • 2020-10-12
              • 2017-03-22
              • 1970-01-01
              • 2012-11-28
              • 2014-11-12
              • 1970-01-01
              相关资源
              最近更新 更多