在到达 ModelBinder 之前推进 Request.InputStream

Ale*_*use 5 c# asp.net-mvc asp.net-mvc-4

在我们的 MVC 2 应用程序中,我们实现了一个 JSON 模型绑定器,如下所示:

    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);
    }
Run Code Online (Sandbox Code Playgroud)

更新到 MVC 4 后,我注意到我们为传入的 JSON 帖子获取了空传入模型。当挖掘时,很明显上游有什么东西正在推进溪流。这很容易修复,就像这样

    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);
    }
Run Code Online (Sandbox Code Playgroud)

但我想知道发生了什么使改变成为必要?之前的实现只是巧合吗?

Vin*_*kal 4

不,之前的实施并非巧合。

ASP.NET MVC 3引入了内置的 JSON 绑定支持,使操作方法能够接收 JSON 编码的数据并将其模型绑定到操作方法参数。

JsonValueProviderFactory于 及以后默认注册ASP.NET MVC 3。值提供程序JSON在模型绑定之前运行,并将请求数据序列化到字典中。然后将字典数据传递到model binder.

让我们看看如何工作。这里是ASP.NET MVC 开源代码JsonValueProviderFactory.csJsonValueProviderFactory中提供的源代码的链接JsonValueProviderFactory

GetDeserializedObject中定义的方法,如果设置为则JsonValueProviderFactory.cs读取,因此它将留在流的末尾。所以这里先调用再调用。由于 已经读取了一次流并将其推进到流的末尾,因此我们需要再次重置streamContent-Typeapplication/jsonRequest.InputStreamGetDeserializedObjectBindModelGetDeserializedObjectRequest.InputStreamRequest.InputStreamBindModel

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;
}
Run Code Online (Sandbox Code Playgroud)