31

Intro:

Web application, ASP.NET MVC 3, a controller action that accepts an instance of POCO model class with (potentially) large field.

Model class:

public class View
{
    [Required]
    [RegularExpression(...)]
    public object name { get; set; }
    public object details { get; set; }
    public object content { get; set; } // the problem field
}

Controller action:

[ActionName(...)]
[Authorize(...)]
[HttpPost]
public ActionResult CreateView(View view)
{
    if (!ModelState.IsValid) { return /*some ActionResult here*/;}
    ... //do other stuff, create object in db etc. return valid result
}

Problem:

An action should be able to accept large JSON objects (at least up to hundred megabytes in a single request and that's no joke). By default I met with several restrictions like httpRuntime maxRequestLength etc. - all solved except MaxJsonLengh - meaning that default ValueProviderFactory for JSON is not capable of handling such objects.

Tried:

Setting

  <system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="2147483647"/>
      </webServices>
    </scripting>
  </system.web.extensions>
  • does not help.

Creating my own custom ValueProviderFactory as described in @Darin's answer here:

JsonValueProviderFactory throws "request too large"

  • also failed because I have no possibility to use JSON.Net (due to non-technical reasons). I tried to implement correct deserialization here myself but apparently it's a bit above my knowledge (yet). I was able to deserialize my JSON string to Dictionary<String,Object> here, but that's not what I want - I want to deserialize it to my lovely POCO objects and use them as input parameters for actions.

So, the questions:

  1. Anyone knows better way to overcome the problem without implementing universal custom ValueProviderFactory?
  2. Is there a possibility to specify for what specific controller and action I want to use my custom ValueProviderFactory? If I know the action beforehand than I will be able to deserialize JSON to POCO without much coding in ValueProviderFactory...
  3. I'm also thinking about implementing a custom ActionFilter for that specific problem, but I think it's a bit ugly.

Anyone can suggest a good solution?

3 Answers 3

68

The built-in JsonValueProviderFactory ignores the <jsonSerialization maxJsonLength="50000000"/> setting. So you could write a custom factory by using the built-in implementation:

public sealed class MyJsonValueProviderFactory : 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();
        serializer.MaxJsonLength = 2147483647;
        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;
    }
}

The only modification I did compared to the default factory is adding the following line:

serializer.MaxJsonLength = 2147483647;

Unfortunately this factory is not extensible at all, sealed stuff so I had to recreate it.

and in your Application_Start:

ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.OfType<System.Web.Mvc.JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new MyJsonValueProviderFactory());
Sign up to request clarification or add additional context in comments.

7 Comments

There are a lot of people tasking about this topic and this is the only solution I could find that worked in my MVC4 Application. Thank you!
Super! Works great for Binding large Json object. For GET requests with large Json object I am using the class over here: brianreiter.org/2011/01/03/…
If you're having trouble posting large Json structures over an ajax post to MVC4 Controller, try this before any other stuff. Tried a lot of other aproaches with no luck and this alone saved my week. Thanks a lot @DarinDimitrov !
works for MVC5 also using .Net 4.5. excellent, spent way to many hours trying to resolve the maxLength issue. this also does not negatively affect JSON.net base controller.
4 years later: Yeah!! That is still the best (only) solution to find, regarding this error... Thanks @Darin
|
18

I found that the maxRequestLength did not solve the problem however. I resolved my issue with the below setting. It is cleaner than having to implement a custom ValueProviderFactory

<appSettings>
  <add key="aspnet:MaxJsonDeserializerMembers" value="150000" />
</appSettings>

Credit goes to the following questions:

JsonValueProviderFactory throws "request too large"

Getting "The JSON request was too large to be deserialized"

This setting obviously relates to a highly complex json model and not the actual size.

2 Comments

This might be useful for someone, although my original issue was not affected by this setting. You had a really complex JSON document with a lot of elements - so that setting helped you - and I had quite simple document with large encoded content for some of the values.
Oliver - You hit the max # of items in the json dictionary rather than content length or complexity. There's a limitation of 1000 items by default in the JavaScriptSerializer. Your answer is correct for this scenario but here's the link on the subject msdn.microsoft.com/en-us/library/hh975440.aspx
3

The solution of Darin Dimitrov works for me but i need reset the position of the stream of the request before read it, adding this line:

controllerContext.HttpContext.Request.InputStream.Position = 0;

So now, the method GetDeserializedObject looks like this:

 private static object GetDeserializedObject(ControllerContext controllerContext)
    {
        if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
        {
            // not JSON request
            return null;
        }
        controllerContext.HttpContext.Request.InputStream.Position = 0;
        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();
        serializer.MaxJsonLength = 2147483647;
        object jsonData = serializer.DeserializeObject(bodyText);
        return jsonData;
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.