反序列化大型json对象时出现JsonMaxLengthexception

介绍:

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无法处理此类对象。

尝试:

设置

         
  • 没有帮助。

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

JsonValueProviderFactory抛出“请求太大”

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

那么,问题:

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

有人可以建议一个好的解决方案

内置的JsonValueProviderFactory忽略设置。 因此,您可以使用内置实现编写自定义工厂:

 public sealed class MyJsonValueProviderFactory : ValueProviderFactory { private static void AddToBackingStore(Dictionary backingStore, string prefix, object value) { IDictionary d = value as IDictionary; if (d != null) { foreach (KeyValuePair 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 backingStore = new Dictionary(StringComparer.OrdinalIgnoreCase); AddToBackingStore(backingStore, String.Empty, jsonData); return new DictionaryValueProvider(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().FirstOrDefault()); ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory()); 

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

    

信用可归结为以下问题:

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