Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Advancing Request.InputStream before reaching ModelBinder

In our MVC 2 app we have a JSON model binder implemented like so:

    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 updating to MVC 4 I noticed that we were getting null incoming models for incoming JSON posts. When digging in it became apparent that something upstream was advancing the stream. This was easy enough to fix, like 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 am wondering what happened that made the change necessary? Was the prior implementation only working by coincidence?

like image 600
AlexCuse Avatar asked Sep 30 '22 07:09

AlexCuse


1 Answers

No, The prior implementation was not working by coincidence.

ASP.NET MVC 3 introduced built-in JSON binding support that enables action methods to receive JSON-encoded data and model-bind it to action-method parameters.

JsonValueProviderFactory is registered by default in ASP.NET MVC 3 and onwards. The JSON value provider runs before model binding and serializes request data into a dictionary. The dictionary data is then passed to the model binder.

Let's see how JsonValueProviderFactory works.Here is link for the source code of JsonValueProviderFactory provided in ASP.NET MVC Open Source code JsonValueProviderFactory.cs

GetDeserializedObject method, defined in JsonValueProviderFactory.cs, reads the stream if the Content-Type is set to application/json and hence it leaves the Request.InputStream at the end of the stream. So here GetDeserializedObject is called first and then BindModel is called. Since GetDeserializedObject has already read the stream once and advanced the Request.InputStream to the end of the stream, we need to reset the Request.InputStream again in BindModel

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;
}
like image 135
Vinkal Avatar answered Oct 13 '22 01:10

Vinkal