Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 5 Increase Max JSON Length in POST Request

I am sending a POST request to an MVC controller with a large amount of JSON data in the body and it is throwing the following:

ArgumentException: Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property. Parameter name: input

To remedy this, I have tried a number of Web.Config solutions. Namely:

<system.web> 
...
<httpRuntime maxRequestLength="2147483647" />
</system.web>

...

<system.web.extensions>
  <scripting>
    <webServices>
      <jsonSerialization maxJsonLength="2147483644"/>
    </webServices>
  </scripting>
</system.web.extensions>

Now, the controller that I am communicating with is in its own Area, with its own Web.Config. I have tried placing the above in either the root or the Area's Web.Config individually, but neither are working. When I debug on a different method within the same controller, I get the default JSON Max Length with the following:

Console.WriteLine(new ScriptingJsonSerializationSection().MaxJsonLength);
// 102400

Here is the method that I am wanting to POST against:

[HttpPost]
public JsonResult MyMethod (string data = "") { //... }

How can I increase the Max JSON Length for the MVC Controller so that my request can successfully reach the method?

Edit: Added <httpRuntime maxRequestLength="2147483647" />

like image 245
aiokos Avatar asked Dec 15 '16 15:12

aiokos


2 Answers

So, although this is a rather disagreeable solution, I got around the problem by reading the request stream manually, rather than relying on MVC's model binders.

For example, my method

[HttpPost]
public JsonResult MyMethod (string data = "") { //... }

Became

[HttpPost]
public JsonResult MyMethod () {
    Stream req = Request.InputStream;
    req.Seek(0, System.IO.SeekOrigin.Begin);
    string json = new StreamReader(req).ReadToEnd();
    MyModel model = JsonConvert.DeserializeObject<MyModel>(json);
    // use model...
}

This way I could use JSON.NET and get around the JSON max length restrictions with MVC's default deserializer.

To adapt this solution, I would recommend creating a custom JsonResult factory that will replace the old in Application_Start().

like image 56
aiokos Avatar answered Sep 18 '22 15:09

aiokos


Problem:

The problem is in JsonValueProviderFactory class in System.Web.Mvc namespace. Actually if you decompile System.Web.Mvc.dll and find the JsonValueProviderFactory class you will see that in GetDeserializedObject methods it has used JavaScriptSerializer without setting any value for MaxJsonLength:

private static object GetDeserializedObject(ControllerContext controllerContext)
{
    if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
    {
        return null;
    }
    StreamReader streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
    string text = streamReader.ReadToEnd();
    if (string.IsNullOrEmpty(text))
    {
        return null;
    }
    // The problem is here, not given. javaScriptSerializer.MaxJsonLength The default value is 2097152 bytes, that is 2. M
    JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
    return javaScriptSerializer.DeserializeObject(text);
}

Solution: You can rewrite the JsonValueProviderFactory class and set javaScriptSerializer.MaxJsonLength and then replace this class in Application_Start() methods in Global.asax like this:

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

Here is full working code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Web.Mvc;
using System.Web.Mvc.Properties;
using System.Web.Script.Serialization;
namespace XXX
{
    public sealed class MyJsonValueProviderFactory : ValueProviderFactory
    {
        private class EntryLimitedDictionary
        {
            private static int _maximumDepth = GetMaximumDepth();
            private readonly IDictionary<string, object> _innerDictionary;
            private int _itemCount;

            public EntryLimitedDictionary(IDictionary<string, object> innerDictionary)
            {
                this._innerDictionary = innerDictionary;
            }

            public void Add(string key, object value)
            {
                if (++this._itemCount > _maximumDepth)
                {
                    //throw new InvalidOperationException(MvcResources.JsonValueProviderFactory_RequestTooLarge);
                    throw new InvalidOperationException("itemCount is over maximumDepth");
                }
                this._innerDictionary.Add(key, value);
            }

            private static int GetMaximumDepth()
            {
                NameValueCollection appSettings = ConfigurationManager.AppSettings;
                if (appSettings != null)
                {
                    string[] values = appSettings.GetValues("aspnet:MaxJsonDeserializerMembers");
                    int result;
                    if (values != null && values.Length > 0 && int.TryParse(values[0], out result))
                    {
                        return result;
                    }
                }
                return 1000;
            }
        }

        private static void AddToBackingStore(EntryLimitedDictionary backingStore, string prefix, object value)
        {
            IDictionary<string, object> dictionary = value as IDictionary<string, object>;
            if (dictionary != null)
            {
                foreach (KeyValuePair<string, object> current in dictionary)
                {
                    AddToBackingStore(backingStore, MakePropertyKey(prefix, current.Key), current.Value);
                }
                return;
            }
            IList list = value as IList;
            if (list != null)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    AddToBackingStore(backingStore, MakeArrayKey(prefix, i), list[i]);
                }
                return;
            }
            backingStore.Add(prefix, value);
        }

        private static object GetDeserializedObject(ControllerContext controllerContext)
        {
            if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
            {
                return null;
            }
            StreamReader streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
            string text = streamReader.ReadToEnd();
            if (string.IsNullOrEmpty(text))
            {
                return null;
            }
            JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();
            // To solve this problem:
            javaScriptSerializer.MaxJsonLength = int.MaxValue;
            // ----------------------------------------
            return javaScriptSerializer.DeserializeObject(text);
        }

        public override IValueProvider GetValueProvider(ControllerContext controllerContext)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            object deserializedObject = GetDeserializedObject(controllerContext);
            if (deserializedObject == null)
            {
                return null;
            }
            Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
            EntryLimitedDictionary backingStore = new EntryLimitedDictionary(dictionary);
            AddToBackingStore(backingStore, string.Empty, deserializedObject);
            return new DictionaryValueProvider<object>(dictionary, CultureInfo.CurrentCulture);
        }

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

        private static string MakePropertyKey(string prefix, string propertyName)
        {
            if (!string.IsNullOrEmpty(prefix))
            {
                return prefix + "." + propertyName;
            }
            return propertyName;
        }
    }
}

References: https://www.fatalerrors.org/a/net-mvc-json-javascriptserializer-string-exceeds-the-maxjsonlength.html

like image 43
Alireza Ahmadi Avatar answered Sep 22 '22 15:09

Alireza Ahmadi