Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JsonMaxLength exception on deserializing large json objects

Intro:

Web application, ASP.NET MVC 3, a controller action that accepts an instance of POCO model class with (potentially) large field.

Model class:

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

Controller action:

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

Problem:

An action should be able to accept large JSON objects (at least up to hundred megabytes in a single request and that's no joke). By default I met with several restrictions like httpRuntime maxRequestLength etc. - all solved except MaxJsonLengh - meaning that default ValueProviderFactory for JSON is not capable of handling such objects.

Tried:

Setting

  <system.web.extensions>     <scripting>       <webServices>         <jsonSerialization maxJsonLength="2147483647"/>       </webServices>     </scripting>   </system.web.extensions> 
  • does not help.

Creating my own custom ValueProviderFactory as described in @Darin's answer here:

JsonValueProviderFactory throws "request too large"

  • also failed because I have no possibility to use JSON.Net (due to non-technical reasons). I tried to implement correct deserialization here myself but apparently it's a bit above my knowledge (yet). I was able to deserialize my JSON string to Dictionary<String,Object> here, but that's not what I want - I want to deserialize it to my lovely POCO objects and use them as input parameters for actions.

So, the questions:

  1. Anyone knows better way to overcome the problem without implementing universal custom ValueProviderFactory?
  2. Is there a possibility to specify for what specific controller and action I want to use my custom ValueProviderFactory? If I know the action beforehand than I will be able to deserialize JSON to POCO without much coding in ValueProviderFactory...
  3. I'm also thinking about implementing a custom ActionFilter for that specific problem, but I think it's a bit ugly.

Anyone can suggest a good solution?

like image 937
Sergey Kudriavtsev Avatar asked Mar 30 '12 13:03

Sergey Kudriavtsev


People also ask

What is the max JSON length?

Property Value The maximum length of JSON strings. The default is 2097152 characters, which is equivalent to 4 MB of Unicode string data.

What is Jsonconvert SerializeObject C#?

SerializeObject Method (Object, Type, JsonSerializerSettings) Serializes the specified object to a JSON string using a type, formatting and JsonSerializerSettings.


1 Answers

The built-in JsonValueProviderFactory ignores the <jsonSerialization maxJsonLength="50000000"/> setting. So you could write a custom factory by using the built-in implementation:

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

The only modification I did compared to the default factory is adding the following line:

serializer.MaxJsonLength = 2147483647; 

Unfortunately this factory is not extensible at all, sealed stuff so I had to recreate it.

and in your Application_Start:

ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<System.Web.Mvc.JsonValueProviderFactory>().FirstOrDefault()); ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory()); 
like image 189
Darin Dimitrov Avatar answered Oct 13 '22 17:10

Darin Dimitrov