C#Http.Response Stream返回带有application/json Content Type的空字符串

9 c# asp.net-mvc json

进行简单的C#单元测试:

[TestMethod]
public void JsonPostTest()
{
  string testUri1 = "http://localhost:1293/Test/StreamDebug";
  string testUri2 = "http://localhost:1293/Test/StreamDebug2?someParameter=abc";

  string sampleJson = @"
  {
    ""ID"": 663941764,
    ""MessageID"": ""067eb623-7580-4d82-bb5c-f5d7dfa69b1e""
  }";

  HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(testUri1);
  EmailConfig config = GetTestConfigLive();

  // Add postmark headers
  request.Accept = "application/json";
  request.ContentType = "application/json";

  request.Method = "POST";
  using (var outStream = new StreamWriter(request.GetRequestStream()))
  {
    outStream.Write(sampleJson);
  }

  // Get response
  HttpWebResponse response = (HttpWebResponse)request.GetResponse();
  string resultText = "";
  using (var reader = new StreamReader(response.GetResponseStream()))
  {
    resultText = reader.ReadToEnd();
  }

  Assert.Inconclusive();
}
Run Code Online (Sandbox Code Playgroud)

还有一组简单的MVC操作来消耗并将发布的数据回送给单元测试(注意两个操作中的代码是相同的):

[HttpPost]
[ValidateInput(false)]
public ActionResult StreamDebug()
{
  string postbody = "";
  using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))
  {
    postbody = reader.ReadToEnd();
  }
  return this.Content(postbody);
}

[HttpPost]
[ValidateInput(false)]
public ActionResult StreamDebug2(string someParameter)
{
  string postbody = "";
  using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))
  {
    postbody = reader.ReadToEnd();
  }
  return this.Content(postbody);
}
Run Code Online (Sandbox Code Playgroud)

如果我发布到第一个动作,我得到一个包含发布的json的字符串,如果我发布到第二个动作,我得到一个空字符串.

更重要的是,如果我将单元测试中的内容类型更改为"text/plain",则两个操作都会返回预期值.

任何人都可以解释为什么会发生这种情况吗?

值得注意的是,在这两种情况下,两种行动的请求长度似乎都是合适的长度.

更多环境信息:单元测试在一个单独的MS测试项目中.操作在一个空的MVC 4.0项目(Net 4.0)中.

And*_*rei 12

可能Request.InputStream已经读取了请求管道中的某个位置.在这种情况下,它的位置已经在结尾,当然ReadToEnd不会读取任何内容并返回空字符串.这是我们案例中问题的根源.重置位置可修复问题:

[HttpPost]
[ValidateInput(false)]
public ActionResult StreamDebug2(string someParameter)
{
  string postbody = "";
  Request.InputStream.Position = 0;
  using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))
  {
    postbody = reader.ReadToEnd();
  }
  return this.Content(postbody);
}
Run Code Online (Sandbox Code Playgroud)

更新.经过一些挖掘资源后,我也发现了为什么这个位置发生了变化.事实证明,Request.InputStream使用在JsonValueProviderFactory以下述方式:

// System.Web.Mvc.JsonValueProviderFactory
private static object GetDeserializedObject(ControllerContext controllerContext)
{
    if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
    {
        return null;
    }
    StreamReader streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
    string text = streamReader.ReadToEnd();
    if (string.IsNullOrEmpty(text))
    {
        return null;
    }
    JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
    return javaScriptSerializer.DeserializeObject(text);
}
Run Code Online (Sandbox Code Playgroud)

This method is called by ControllerActionInvoker to retrieve values from request and bind them to action parameters. Note that this is the only place where Request.InputStream is used through all the MVC.

Therefore if content type of the request is json, the method above is called, input stream gets shifted and attempt to read it yet again without resetting the position fails. However when content type is a plain text, MVC does not try to read the request using json deserialization, input stream is not read before the call in controller and everything works as expected.