Reputation:
Having a simple C# unit test:
[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();
}
And a simple set of MVC actions to consume and echo posted data back to the unit test (Notice that the code in both actions is identical):
[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);
}
If I post to the first action I get a string containing the posted json, if I post to the second action I get an empty string.
To make matters more interesting, if I change the content type in the unit test to "text/plain", both actions return the expected values.
Can anyone shed any light on why this might be happening?
Its also worth noting that the request length on the both actions under both sets of circumstances seems to be of the right length.
Further environmental information: Unit test is in a separate MS test project. Actions are in a empty MVC 4.0 project (Net 4.0).
Upvotes: 10
Views: 7734
Reputation: 56716
It is possible that somewhere in the request pipeline Request.InputStream
was already read. In this case its position is already at the end, and of course ReadToEnd
reads nothing and returns empty string. This is the root of the problem in our case. Resetting the position fixes the problem:
[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);
}
Update. After a little bit of digging into sources I also found why the position was shifted. It turns out that Request.InputStream
is used in JsonValueProviderFactory
in the following manner:
// 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);
}
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.
Upvotes: 13