【问题标题】:JsonMaxLength exception on deserializing large json objects反序列化大型 json 对象的 JsonMaxLength 异常
【发布时间】:2012-04-14 04:23:36
【问题描述】:

简介:

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 对象(在单个请求中至少达到数百兆字节,这不是开玩笑)。默认情况下,我遇到了一些限制,例如 httpRuntime maxRequestLength 等 - 除了 MaxJsonLengh 之外,所有限制都已解决 - 这意味着 JSON 的默认 ValueProviderFactory 无法处理此类对象。

试过了:

设置

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

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

JsonValueProviderFactory throws "request too large"

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

那么问题来了:

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

谁能提出一个好的解决方案?

【问题讨论】:

    标签: c# asp.net-mvc json asp.net-mvc-3


    【解决方案1】:

    内置的 JsonValueProviderFactory 会忽略 &lt;jsonSerialization maxJsonLength="50000000"/&gt; 设置。因此,您可以使用内置实现来编写自定义工厂:

    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());
    

    【讨论】:

    • 有很多人在处理这个主题,这是我能找到的唯一在我的 MVC4 应用程序中工作的解决方案。谢谢!
    • 超级!非常适合绑定大型 Json 对象。对于带有大型 Json 对象的 GET 请求,我在这里使用该类:brianreiter.org/2011/01/03/…
    • 如果您在将大型 Json 结构通过 ajax 发布到 MVC4 控制器时遇到问题,请先尝试此操作。在没有运气的情况下尝试了很多其他方法,仅此一项就挽救了我的一周。非常感谢@DarinDimitrov!
    • 适用于 MVC5 也使用 .Net 4.5。很好,花了很多时间试图解决 maxLength 问题。这也不会对 JSON.net 基本控制器产生负面影响。
    • 4 年后:是啊!!关于这个错误,这仍然是最好的(唯一)解决方案......谢谢@Darin
    【解决方案2】:

    但我发现 maxRequestLength 并没有解决问题。 我通过以下设置解决了我的问题。它比必须实现自定义 ValueProviderFactory

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

    归功于以下问题:

    JsonValueProviderFactory throws "request too large"

    Getting "The JSON request was too large to be deserialized"

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

    【讨论】:

    • 这可能对某人有用,尽管我最初的问题不受此设置的影响。您有一个非常复杂的 JSON 文档,其中包含很多元素 - 因此该设置对您有所帮助 - 我有一个非常简单的文档,其中一些值的编码内容很大。
    • Oliver - 您达到了 json 字典中的最大项目数,而不是内容长度或复杂性。 JavaScriptSerializer 中默认限制为 1000 个项目。对于这种情况,您的答案是正确的,但这是主题 msdn.microsoft.com/en-us/library/hh975440.aspx 的链接
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-01-09
      • 2019-07-21
      • 1970-01-01
      • 2016-10-08
      • 2019-08-22
      • 1970-01-01
      • 2015-12-09
      相关资源
      最近更新 更多