So You Need to Expose JSON Endpoints

Even the most experienced programmers can be caught unawares by software they've used for years without trouble. Dino Esposito explains why and how the JSON method in an ASP.NET MVC controller class suddenly started to cause an exception on a production server, and how he fixed the problem.

Everybody today needs to expose or publish JSON data out of a web server for some JavaScript client-side code to consume. At first, it even sounds as if it doesn’t require much thought: It’s a common requirement and so there are many ways of doing it to choose from. Because it is confusing for developers to make that initial choice, they tend to just pick one and continue to use it: Until, it suddenly goes wrong…

Options for Exposing JSON Content

To expose JSON, you just need an HTTP callable endpoint and some logic behind it that arranges ad-hoc content of the specific content type: application/json. This HTTP-callable endpoint can be one of many types, including, for example

  • a Web Forms ASPX page
  • an ASP.NET MVC controller method
  • a WCF service,
  • an ASMX web service,
  • a PHP page.

In the ASP.NET space, most Web Forms solutions rely on WCF services to expose JSON data because WCF was, in the past, the only way to attach services to Web Forms solutions. Those Web Forms applications that were recently written, or significantly restructured, probably use Web API to expose HTTP-callable endpoints. If you already have WCF around, stick to that for your Web Forms. Otherwise, reference the Web API infrastructure and start adding Web API controllers to the solution.

To be picky, you should avoid mixing WCF and Web API frameworks together. This is not because of well or little known incompatibilities. More simply, it’s just to prevent the footprint of your application from growing too large.

When it comes to ASP.NET MVC, however, the decision is more confusing. You still have two main options, but the distinctions between them are blurred. One option consists in using a plain controller method that uses the ASP.NET MVC infrastructure to serialize .NET objects to JSON. The other option involves using the Web API infrastructure. Let’s review both approaches, because behind the apparent simplicity lurks a potential problem.

The MVC Serialization Issue

In ASP.NET MVC, the Controller class-the root of all controllers-has a method called Json with the following signature:

The data parameter must be a serializable object, and ASP.NET MVC will write the resulting JSON string to the response stream. It is interesting to notice that the actual JSON serialization is performed by the JavaScriptSerializer class. Here’s some sample code that shows an HTTP endpoint returning JSON:

The method uses any available logic to capture the list of objects to serialize and then just passed it down to the Json method for the actual serialization. It’s neat, easy and effective; well, sort of.

Up until recently, I never ran into any problems with the JSON and ASP.NET MVC. I’m not sure if this was pure luck or excellent forethought on my part, but the fact is that the size of the returned JSON has never been an issue. Well, until recently.

If you take a look at the source code ASP.NET MVC and in particular at the code of the JsonResult class, you find the following snippet:

This code snippet is an excerpt from the ExecuteResult method in the JsonResult class. You can explore the same code on your own by pointing the browser at the following URL:

http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/JsonResult.cs.

As you can see, the code depends on a couple of parameters: MaxJsonLength and RecursionLimit. Both are nullable integer properties on the JsonResult class. Both these are proxy-analogous properties defined on the underlying JavaScriptSerializer class that ASP.NET MVC uses for actual JSON serialization. If either of those properties has a non-null value, then that value is passed over to JavaScriptSerializer; otherwise, the default values apply. What, than are these default values? In JavaScriptSerializer, the MaxJsonLength property is set to 2MB and RecursionLimit is set to 100.

The RecursionLimit property indicates the maximum nesting level allowed while serializing a hierarchy of objects or traversing a JSON string during deserialization. Overall, a default value of 100 seems overly safe as no savvy developer would ever bear more than 4 or 5 levels of nesting. The default value set on MaxJsonLength is a bit different and while it hit me personally only recently I could easily find out, on StackOverflow, that this limit had hit many other in the past.

Customizing the MaxJsonLength Property

I admit that I have no idea why JavaScriptSerializer is used to perform JSON serialization in ASP.NET MVC. Likewise, I have no clues either as to why a default boundary was set on the size of data and at only 2MB. But that’s the reality, after all. The moment in which your code attempts to serialize (or deserialize) more than 2MB of data, an exception is raised, as below.

2294-2805eaa2-92df-4722-ac24-1617f22d1b2

There’s just one possible action to take that could solve the problem: raising the threshold of MaxJsonLength. Unsurprisingly, there are many ways to implement that. However, most developers – myself included – love to serialize data via the simple Json method from within the controller method.

It works well except that it doesn’t give you any control over the maximum length of the JSON string. At the end of the day, the Json method is only a piece of syntactic sugar set around the constructor of the JsonResult class. By merely calling the JsonResult constructor directly, you gain a lot more control over the available properties.

Alternatively, you can perform the serialization yourself through the same JavaScriptSerializer class, or any other JSON serializer you may know and love. If you wish to stick to JavaScriptSerializer, here’s the code you need:

This approach is probably the most powerful because it lets you gain full control over the entire serialization process. Not only can you control the maximum length of the data to process but also you can customize the JSON processor to register type converters. In this way, you can choose the way that dates or other types are handled. You can even instruct the serializer how to treat null and empty strings. More drastically, you can even replace JavaScriptSerializer with any Json serialiser that you prefer, such as the NewtonSoft JSON serializer.

Note that if you decide to handle the serialization yourself, you can’t use JsonResult to wrap up the results for the caller. Once serialized, the original object is just a plain string that you package up using an instance of the more generic ContentResult class.

Note that, if you return the serialized JSON string through the method Json, the structure of the returned data won’t be altered. A new instance of the JavaScriptSerializer class will be created though in the folds of the method Json: This will bring you back to square one, and subject to the default length limitations of the JavaScriptSerializer class.

Application Do’s and Don’ts

I used the Json method of the Controller class for years and never experienced any sort of problem. Now I believe that I avoided problems only because I managed to keep any JSON chunk of data well below the threshold of 2MB.

When I hit the 2MB limit, I could have shrugged and reduced the size of the Json document or increased the limit by altering the code. At this point, though, my natural curiosity took charge and I felt compelled to find out whether 2MB is reasonable threshold, and whether it is reasonable to return more than 2MB of JSON data.

Some quick research showed a general consensus was that a current browser could handle up to around 1.5MB of JSON data sets without slowing down or showing side effects. Support for JSON also seems to be quite similar across the entire spectrum of available browsers. A 2MB default threshold therefore sounds as if it is wise choice.

So 2MB seems reasonable for a default threshold. Should your application reasonably request more than 2MB of JSON data? While the workarounds discussed so far enable your HTTP service to happily return several MB of data, I’m still convinced that no clients should ask for more than 1.5MB and probably far less.

I would like to suggest, from my personal experience with the JSON limitation in the JavaScriptSerializer class, that the answer is to reduce, whenever possible, the amount of data being passed. Although application-specific exceptions may apply, all applications should be very careful about the amount of data they download and should ensure that the amount of downloaded data is always justified by the use-case. The specific application I was developing needed a certain amount of data to be loaded at startup. The size of the data is not fixed and the amount of stored data grows as the system is used. When I first experienced the problem it was simply because, for the first time in three years of production, the initial load of data exceeded 2MB-it was only 1K extra but enough to crash the system. (Needless to say, there was no serious compensation logic around the request because it had never been considered an issue.) More recently I worked on the mobile frontend for the same system and built it from scratch. When I planned the startup module and the initial load of data I made of point of having a mobile-specific endpoint compacting the JSON data as much as possible. I wasn’t aware of the JSON length boundary, but I just did it because it seemed a good design and programming practice. In internal reports, I emphasized how the team managed to cut 30% of unessential data out of the JSON initial download by applying transformations that were conceptually close to the minification of script and CSS files. The team properly handled NULL values and replaced them with empty strings and also renamed properties to keep the payload smaller. In doing so, we didn’t make the mobile frontend immune from the 2MB threshold of ASP.NET MVC JSON controller methods but by adopting good programming practices we delayed the occurrence of the issue of probably several months, if not a few years.

Web API Maybe?

As an ASP.NET MVC developer, I’ve never been a huge fan of Web API. I recognize the relevance of Web API and the role it plays in Web Forms development and when you design the node of a service-oriented architecture. However. But if you are building an ASP.NET MVC application and you need to expose a few JSON endpoints, then I reckon Web API overkill, though fully functional. In addition, Web API doesn’t use JavaScriptSerializer to create JSON payloads. It relies on the services of the NewtonSoft component and has no predefined limitation on the size of data.

In the context of a plain ASP.NET MVC solution, Web API is an extra cost as it requires the instantiation of an additional pipeline to process its requests. Until the ASP.NET 5 framework becomes publicly available and reliable to use, Web API and ASP.NET applications have different runtime environments. It is only in ASP.NET 5 that you will find that when targeting the .NET Framework Core, ASP.NET MVC and Web API share the same runtime. At that point, though, there will be just one type of Controller class-as opposed to what you have today-that sums up the features of ASP.NET MVC and Web API controllers of today.

Summary

Particularly where software is concerned, we don’t live in a perfect world. There are many ways of doing things and many shortcuts that sometimes developers use without knowing that it is a shortcut and without being aware of the implicit restrictions. For years I’ve chosen to use the Json method in an ASP.NET MVC controller class without being aware of any side effects, until I eventually lost a night’s sleep while making sense of a weird misbehavior in a production system.