C# Http.Response 流返回内容类型为 application/json 的空字符串

标签 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();
}

还有一组简单的 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);
}

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

为了让事情变得更有趣,如果我将单元测试中的内容类型更改为“文本/纯文本”,这两个操作都会返回预期值。

谁能阐明为什么会发生这种情况?

还值得注意的是,在两种情况下,两种操作的请求长度似乎都是合适的长度。

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

最佳答案

可能请求管道中的某处 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);
}

更新。在对资源进行了一些挖掘之后,我还发现了位置发生变化的原因。事实证明,Request.InputStreamJsonValueProviderFactory 中的使用方式如下:

// 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);
}

此方法由 ControllerActionInvoker 调用以从请求中检索值并将它们绑定(bind)到操作参数。请注意,这是整个 MVC 中唯一使用 Request.InputStream 的地方。

因此,如果请求的内容类型是 json,则调用上面的方法,输入流被移动并尝试再次读取它而不重置位置失败。然而,当内容类型是纯文本时,MVC 不会尝试使用 json 反序列化读取请求,在调用 Controller 之前不会读取输入流,一切都按预期进行。

关于C# Http.Response 流返回内容类型为 application/json 的空字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17317929/

相关文章:

javascript - 使用 jquery 从 displayfor 获取值

c# - 使用带有动态匿名对象的 NHibernate 在 GroupBy 查询中进行选择

javascript - 从kendo multiselect中获取选定的数据数组

javascript - 如何使用 REST API google 脚本从 E-goi 加载数据

javascript - 关于请求响应的非常基本的 AJAX

java - 测试正确 JSON 序列化的最佳方法

c# - 在静态方法中使用语句/调用 Dispose

c# - 根据内容自动调整 GridView 列的宽度

c# - 是否可以在高于每个 Controller 的级别禁用 MVC OutputCache?

asp.net-mvc - MVC + 存储库模式 - 仍然依赖于数据模型?