MVC 5 Increase Max JSON Length in POST Request

11,828

Solution 1

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

Solution 2

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

Share:
11,828
treeblah
Author by

treeblah

Software Engineer

Updated on July 29, 2022

Comments

  • treeblah
    treeblah over 1 year

    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" />

  • Tyriddik
    Tyriddik over 7 years
    I have a rather large JSON string that send in 22 different transactions. I set them as pages in my json formatting. You could try that if the solution above doesn't set well with you.
  • treeblah
    treeblah over 7 years
    That's a good idea. I think I'll stick with this solution for now until I override the default MVC JSON serializer with JSON.NET's. It's super unlikely that a request of this size would occur in the first place.
  • RandallTo
    RandallTo over 6 years
    I was about ready to switch everything to another technology, scraping the use of angular-base64-upload, until I found this work around as none of the web.config size settings had any effect. Thanks!.
  • gurkan
    gurkan over 4 years
    I love this hack. Tested all of the above web.config settings but nothing helped. This made the day!
  • Kyle Whittington
    Kyle Whittington over 4 years
    Same as @gurkan above -- I tried everything else and this was the only thing that worked.
  • Matt G
    Matt G almost 4 years
    Still no good solution for this in MVC 5 perhaps there is in .Net Core, but you can now use Request.Form and Request.Form.Get("") to get the posted data instead of using the stream directly. Note: this form property works with both posted forms and ajax post calls.