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

本文关键字:JsonMaxLength 异常 大型 json 对象 反序列化 | 更新日期: 2023-09-27 18:29:27

简介:

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
}

控制器动作:

[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
}

问题:

一个操作应该能够接受大型JSON对象(在一个请求中至少可以达到100MB,这不是开玩笑的)。默认情况下,我遇到了一些限制,如httpRuntime maxRequestLength等——除了MaxJsonLengh之外,所有限制都得到了解决——这意味着JSON的默认ValueProviderFactory无法处理此类对象。

已尝试:

设置

  <system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="2147483647"/>
      </webServices>
    </scripting>
  </system.web.extensions>
  • 没有帮助

创建我自己的自定义ValueProviderFactory,如@Darin在这里的回答所述:

JsonValueProviderFactory抛出";请求太大";

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

所以,问题是:

  1. 有人知道在不实现通用自定义ValueProviderFactory的情况下解决问题的更好方法吗
  2. 是否可以指定我想要使用自定义ValueProviderFactory的特定控制器和操作?如果我事先知道操作,我将能够将JSON反序列化为POCO,而无需在ValueProviderFactory中进行太多编码
  3. 我也在考虑为这个特定的问题实现一个自定义ActionFilter,但我认为这有点难看

有人能提出一个好的解决方案吗?

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

内置的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;
    }
}

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

serializer.MaxJsonLength = 2147483647;

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

以及在您的Application_Start:中

ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<System.Web.Mvc.JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory());

我发现maxRequestLength并不能解决问题。我通过以下设置解决了问题。它比必须实现自定义ValueProviderFactory更干净

<appSettings>
  <add key="aspnet:MaxJsonDeserializerMembers" value="150000" />
</appSettings>

以下问题值得称赞:

JsonValueProviderFactory抛出";请求太大";

得到"JSON请求太大,无法反序列化";

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

Darin Dimitrov的解决方案对我有效,但我需要在读取请求之前重置请求流的位置,添加此行:

controllerContext.HttpContext.Request.InputStream.Position = 0;

现在,GetDeserializedObject方法看起来是这样的:

 private static object GetDeserializedObject(ControllerContext controllerContext)
    {
        if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
        {
            // not JSON request
            return null;
        }
        controllerContext.HttpContext.Request.InputStream.Position = 0;
        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;
    }