.NET MVC – JavaScriptSerializer string exceeds the maxJsonLength – JSON POST

Background

I recently encountered a maxJsonLength error when large JSON payloads were sent from the Frontend to the Backend. By debugging, I could see that the controller action was not entered, suggesting the issue was with the JSON deserialization to the desired object failing when trying to execute it.

Difficulties

When data is sent from the Backend to the Frontend and a maxJsonLength error is observed, it can usually be fixed by adding or updating settings in the web.config file, such as ‘maxJsonLength’ or ‘aspnet:MaxJsonDeserializerMembers’. 

These settings are the suggested fixes most encountered when trying to search Google/StackOverflow for a resolution. These config changes, however, did not work for this scenario. 

Solution

The issue is that the default JsonValueProviderFactory has a limit of 2MB (2097152 bytes). To use a larger maximum JSON length, a custom JsonValueProviderFactory will have to be built, that explicitly sets the MaxJsonLength property of the JavaScriptSerializer it uses.

Create CustomJsonValueProviderFactory.cs with the following class (set the MaxJsonLength to the desired value):

public sealed class CustomJsonValueProviderFactory : 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();
        // Set MaxJsonLength
        serializer.MaxJsonLength = int.MaxValue;
        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;
    }
}

In the Global.asax Application_Start() method, add the following to replace the default JsonValueProviderFactory with the custom implementation:

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

With these changes, when large (> 2MB) JSON payloads can be deserialized and the controller action entered.