Fading Coder

An Old Coder’s Final Dance

You are here: Home > Tech > Content

.NET MVC: Resolve JavaScriptSerializer maxJsonLength for Large JSON Request Bodies

Tech 4

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.

Tags: aspnet-mvc

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.