Promoting a request. Input stream before reaching ModelBinder
In our MVC 2 app, we have a JSON binder implemented like this:
public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string input;
using (var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream))
{
input = reader.ReadToEnd();
}
return JsonConvert.DeserializeObject(
input,
bindingContext.ModelType);
}
After upgrading to MVC 4, I noticed that we are getting empty incoming models for incoming JSON messages. When digging into it it became apparent that something was upstream propelling the stream. It was easy enough to fix, so
public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string input;
//something upstream after MVC 4 upgrade is advancing the stream to end before we can read it
controllerContext.HttpContext.Request.InputStream.Position = 0;
using (var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream))
{
input = reader.ReadToEnd();
}
return JsonConvert.DeserializeObject(
input,
bindingContext.ModelType);
}
But I'm wondering what happened, what made the change necessary? Was the previous implementation only working by coincidence?
source to share
No, the previous implementation did not work by coincidence.
ASP.NET MVC 3
there is built-in support for JSON binding, which allows action methods to receive data in JSON encoding and bind a model to action method parameters.
JsonValueProviderFactory
by default logs in ASP.NET MVC 3
or more JsonValueProviderFactory
. The value provider JSON
runs before binding the model and serializes the request data into a dictionary. The dictionary data is then passed to model binder
.
Let's see how it works JsonValueProviderFactory
Here is a link to the source code JsonValueProviderFactory
provided in ASP.NET MVC Open source JsonValueProviderFactory.cs
The method GetDeserializedObject
defined in JsonValueProviderFactory.cs
reads stream
if Content-Type
set to, application/json
and therefore leaves Request.InputStream
at the end of the stream. So here GetDeserializedObject
is called GetDeserializedObject
and then BindModel
. Since we GetDeserializedObject
have already read the stream once and pushed Request.InputStream
to the end of the stream, we need to flush Request.InputStream
toBindModel
private static object GetDeserializedObject(ControllerContext controllerContext)
{
if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
{
// not JSON request
return null;
}
StreamReader reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
string bodyText = reader.ReadToEnd();
if (String.IsNullOrEmpty(bodyText))
{
// no JSON data
return null;
}
JavaScriptSerializer serializer = new JavaScriptSerializer();
object jsonData = serializer.DeserializeObject(bodyText);
return jsonData;
}
source to share