.NET MVC: Resolve JavaScriptSerializer maxJsonLength for Large JSON Request Bodies
When posting large JSON payloads to an ASP.NET MVC action, model binding may fail with an ArgumentException similar to:
- An error occurred during JSON JavaScriptSerializer serialization or deserialization. The length of the string exceeds the value set on the maxJsonLength property
- Stack shows System.Web.Mvc.JsonValueProviderFactory.GetDeserializedObject
Common web.config tweaks do not fix this:
- appSettings keys like aspnet:MaxJsonDeserializerMembers or aspnet:UpdatePanelMaxScriptLength
- system.web.extensions/scripting/webServices/jsonSerialization maxJsonLength
These settings don’t apply because MVC’s JsonValueProviderFactory creates a new JavaScriptSerializer directly and does not read jsonSerialization config. By default, JavaScriptSerializer.MaxJsonLength is 2097152 (2 MB), which triggers the exception for bigger inputs.
Root cause in MVC
JsonValueProviderFactory reads the request body and deserializes it using a fresh JavaScriptSerializer instance without adjusting MaxJsonLength:
// Simplified illustration of MVC's behavior
var body = new StreamReader(context.HttpContext.Request.InputStream).ReadToEnd();
if (!string.IsNullOrEmpty(body))
{
var serializer = new JavaScriptSerializer(); // MaxJsonLength = 2 MB by default
var data = serializer.DeserializeObject(body); // throws on large JSON
}
The aspnet:MaxJsonDeserializerMembers appSetting only constrains the number of entries (depth/size) during falttening, not the input string length.
Solution: Replace the JSON value provider and raise MaxJsonLength
Create a custom ValueProviderFactory that mirrors MVC’s original logic but sets JavaScriptSerializer.MaxJsonLength to int.MaxValue.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using System.Web.Script.Serialization;
namespace YourNamespace
{
public sealed class LargeJsonValueProviderFactory : ValueProviderFactory
{
private sealed class LimitedEntryMap
{
private static readonly int MaxEntries = ResolveMaxEntries();
private readonly IDictionary<string, object> _store;
private int _count;
public LimitedEntryMap(IDictionary<string, object> store)
{
_store = store;
}
public void Add(string key, object value)
{
_count++;
if (_count > MaxEntries)
{
throw new InvalidOperationException("Too many JSON members in request.");
}
_store.Add(key, value);
}
private static int ResolveMaxEntries()
{
NameValueCollection cfg = ConfigurationManager.AppSettings;
if (cfg != null)
{
var values = cfg.GetValues("aspnet:MaxJsonDeserializerMembers");
if (values != null && values.Length > 0 && int.TryParse(values[0], out int n))
{
return n;
}
}
return 1000; // MVC default
}
}
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null) throw new ArgumentNullException(nameof(controllerContext));
var payload = ReadJsonBody(controllerContext);
if (payload == null) return null;
var data = DeserializeJson(payload);
var bag = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
var limited = new LimitedEntryMap(bag);
Flatten(limited, string.Empty, data);
return new DictionaryValueProvider<object>(bag, CultureInfo.CurrentCulture);
}
private static string ReadJsonBody(ControllerContext ctx)
{
var request = ctx.HttpContext.Request;
var contentType = request.ContentType ?? string.Empty;
if (!contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
{
return null;
}
using (var reader = new StreamReader(request.InputStream))
{
var text = reader.ReadToEnd();
return string.IsNullOrWhiteSpace(text) ? null : text;
}
}
private static object DeserializeJson(string json)
{
var serializer = new JavaScriptSerializer
{
MaxJsonLength = int.MaxValue // critical change
};
return serializer.DeserializeObject(json);
}
private static void Flatten(LimitedEntryMap target, string prefix, object value)
{
if (value is IDictionary<string, object> obj)
{
foreach (var kv in obj)
{
Flatten(target, MakePropertyKey(prefix, kv.Key), kv.Value);
}
return;
}
if (value is IList list)
{
for (int i = 0; i < list.Count; i++)
{
Flatten(target, MakeArrayKey(prefix, i), list[i]);
}
return;
}
target.Add(prefix, value);
}
private static string MakeArrayKey(string prefix, int index)
=> prefix + "[" + index.ToString(CultureInfo.InvariantCulture) + "]";
private static string MakePropertyKey(string prefix, string name)
=> string.IsNullOrEmpty(prefix) ? name : prefix + "." + name;
}
}
Register the custom factory
Replace MVC’s built‑in JsonValueProviderFactory in Application_Start so requests are deserialized with the new limit.
using System.Linq;
using System.Web.Mvc;
protected void Application_Start()
{
var existing = ValueProviderFactories.Factories
.OfType<JsonValueProviderFactory>()
.SingleOrDefault();
if (existing != null)
{
ValueProviderFactories.Factories.Remove(existing);
}
// Put ours at the front so it runs early
ValueProviderFactories.Factories.Insert(0, new LargeJsonValueProviderFactory());
}
With the custom factory in place, MVC will accept large JSON request bodies during model binding without hitting the 2 MB default limit.