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

本文关键字:类型 字符串 json Response 返回 application C#Http | 更新日期: 2023-09-27 18:20:07

进行一个简单的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的字符串,如果我发布至第二个操作,则会得到一条空字符串。

更有趣的是,如果我将单元测试中的内容类型更改为"text/plain",那么这两个操作都会返回预期值。

有人能解释为什么会发生这种情况吗?

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

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

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

可能已经读取了请求管道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调用此方法从请求中检索值,并将它们绑定到操作参数。请注意,这是Request.InputStream在所有MVC中使用的唯一位置。

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