进行简单的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)中。
答案 0 :(得分: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);
}
更新。经过一些挖掘,我也发现了为什么这个位置发生了变化。事实证明,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);
}
ControllerActionInvoker
调用此方法以从请求中检索值并将它们绑定到操作参数。请注意,这是唯一通过所有MVC使用Request.InputStream
的地方。
因此,如果请求的内容类型是json,则调用上面的方法,输入流被移位并尝试再次读取它而不重置位置失败。但是,当内容类型是纯文本时,MVC不会尝试使用json反序列化来读取请求,在控制器中调用之前不会读取输入流,并且一切都按预期工作。