反序列化大型json对象时出现JsonMaxLength异常

Ser*_*sev 28 c# asp.net-mvc json asp.net-mvc-3

介绍:

Web应用程序,ASP.NET MVC 3,一种控制器操作,它接受具有(可能)大字段的POCO模型类的实例.

型号类:

public class View
{
    [Required]
    [RegularExpression(...)]
    public object name { get; set; }
    public object details { get; set; }
    public object content { get; set; } // the problem field
}
Run Code Online (Sandbox Code Playgroud)

控制器动作:

[ActionName(...)]
[Authorize(...)]
[HttpPost]
public ActionResult CreateView(View view)
{
    if (!ModelState.IsValid) { return /*some ActionResult here*/;}
    ... //do other stuff, create object in db etc. return valid result
}
Run Code Online (Sandbox Code Playgroud)

问题:

一个动作应该能够接受大型JSON对象(在单个请求中至少高达100兆字节,这不是一个笑话).默认情况下,我遇到了几个限制httpRuntime maxRequestLength等等 - 除了MaxJsonLengh之外都解决了 - 这意味着JSON的默认ValueProviderFactory无法处理这些对象.

尝试:

设置

  <system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="2147483647"/>
      </webServices>
    </scripting>
  </system.web.extensions>
Run Code Online (Sandbox Code Playgroud)
  • 没有帮助.

按照@ Darin的答案中的描述创建我自己的自定义ValueProviderFactory:

JsonValueProviderFactory抛出"请求太大"

  • 也失败了,因为我没有可能使用JSON.Net(由于非技术原因).我试图在这里实现正确的反序列化,但显然它有点超出我的知识(还).我能够将我的JSON字符串反序列化到Dictionary<String,Object>这里,但这不是我想要的 - 我想将它反序列化为我可爱的POCO对象并将它们用作动作的输入参数.

所以,问题:

  1. 任何人都知道更好的方法来克服这个问题而不实现通用的自定义ValueProviderFactory?
  2. 是否有可能指定我想使用我的自定义ValueProviderFactory的具体控制器和操作?如果我事先知道了这个动作,那么我可以将JSON反序列化为POCO,而无需在ValueProviderFactory中进行太多编码......
  3. 我也在考虑为这个特定问题实现自定义ActionFilter,但我觉得它有点难看.

有谁能建议一个好的解决方案?

Dar*_*rov 66

内置的JsonValueProviderFactory忽略该<jsonSerialization maxJsonLength="50000000"/>设置.因此,您可以使用内置实现编写自定义工厂:

public sealed class MyJsonValueProviderFactory : ValueProviderFactory
{
    private static void AddToBackingStore(Dictionary<string, object> backingStore, string prefix, object value)
    {
        IDictionary<string, object> d = value as IDictionary<string, object>;
        if (d != null)
        {
            foreach (KeyValuePair<string, object> entry in d)
            {
                AddToBackingStore(backingStore, MakePropertyKey(prefix, entry.Key), entry.Value);
            }
            return;
        }

        IList l = value as IList;
        if (l != null)
        {
            for (int i = 0; i < l.Count; i++)
            {
                AddToBackingStore(backingStore, MakeArrayKey(prefix, i), l[i]);
            }
            return;
        }

        // primitive
        backingStore[prefix] = value;
    }

    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();
        serializer.MaxJsonLength = 2147483647;
        object jsonData = serializer.DeserializeObject(bodyText);
        return jsonData;
    }

    public override IValueProvider GetValueProvider(ControllerContext controllerContext)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }

        object jsonData = GetDeserializedObject(controllerContext);
        if (jsonData == null)
        {
            return null;
        }

        Dictionary<string, object> backingStore = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        AddToBackingStore(backingStore, String.Empty, jsonData);
        return new DictionaryValueProvider<object>(backingStore, CultureInfo.CurrentCulture);
    }

    private static string MakeArrayKey(string prefix, int index)
    {
        return prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
    }

    private static string MakePropertyKey(string prefix, string propertyName)
    {
        return (String.IsNullOrEmpty(prefix)) ? propertyName : prefix + "." + propertyName;
    }
}
Run Code Online (Sandbox Code Playgroud)

与默认工厂相比,我做的唯一修改是添加以下行:

serializer.MaxJsonLength = 2147483647;
Run Code Online (Sandbox Code Playgroud)

不幸的是,这个工厂根本不可扩展,密封的东西,所以我不得不重新创建它.

在你的Application_Start:

ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<System.Web.Mvc.JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory());
Run Code Online (Sandbox Code Playgroud)

  • 如果你在ajax帖子上发布大型Json结构到MVC4控制器时遇到问题,那么在任何其他事情之前尝试这个.尝试了很多其他没有运气的方法,仅此一个节省了我的一周.非常感谢@DarinDimitrov! (3认同)
  • 有很多人都在谈论这个话题,这是我能找到的唯一可以在我的MVC4应用程序中使用的解决方案.谢谢! (2认同)
  • 超!适用于绑定大型Json对象.对于具有大型Json对象的GET请求,我在这里使用该类:http://brianreiter.org/2011/01/03/custom-jsonresult-class-for-asp-net-mvc-to-avoid-maxjsonlength-exceeded -例外/ (2认同)

Oli*_*ver 17

我发现maxRequestLength并没有解决问题.我用以下设置解决了我的问题.它比必须实现自定义ValueProviderFactory更清晰

<appSettings>
  <add key="aspnet:MaxJsonDeserializerMembers" value="150000" />
</appSettings>
Run Code Online (Sandbox Code Playgroud)

信用可归结为以下问题:

JsonValueProviderFactory抛出"请求太大"

获取"JSON请求太大而无法反序列化"

此设置显然与高度复杂的json模型有关,而与实际大小无关.

  • 尽管我的原始问题不受此设置的影响,但这可能对某些人有用.你有一个非常复杂的JSON文档,里面有很多元素 - 所以这个设置对你有帮助 - 而且我有一个非常简单的文档,其中包含一些值的大编码内容. (2认同)