Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Large JSON Array Streaming in ASP.NET Web API

0.00/5 (No votes)
5 May 2017 1  
An example about streaming large JSON array in ASP.NET Web API and HTTP chunked transfer encoding

Overview

When it comes to streaming a large array of objects (for example, 10,000 objects), we are usually required to deal with two major performance issues:

  1. Large memory allocation for objects
  2. Long response time from server

To deal with the issues, we have two methods that can improve server side performance:

  1. Iterative Pattern in C#
  2. Chunked Transfer Encoding in HTTP

In the following sections, we will take a look at these methods to see how they help two issues out. We will also see two examples working on the array streaming, from server side to client side.

Iterative Pattern in C#

It has been pretty well known that we can enable Iterative Pattern by using yield keyword within a method or property which has IEnumerable(T) return type. The idea behind the pattern is to enumerate each of the items instead of returning the whole collection.

public IEnumerable<ReturnModel> Get()
{
    // An example of returning large number of objects
    foreach (var i in Enumerable.Range(0, 10000))
       yield return new ReturnModel() { SequenceNumber = i, ID = Guid.NewGuid() };
}

Because the enumeration starts as soon as the foreach loop goes without waiting for all objects to be ready, we can expect that the efficiency and memory use are better in general.

Chunked Transfer Encoding in HTTP

Chunked Transfer Encoding is a mechanism that allows the server to return the data "piece by piece". In the encoding, data are separated by each hexadecimal number followed by a "\r\n" which tells the client the length of the following chunk. Below is a server response example given by Mozilla Developer Network that consists of three lines and each line is a chunk.

HTTP/1.1 200 OK 
Content-Type: text/plain 
Transfer-Encoding: chunked

7\r\n
Mozilla\r\n 
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n 
\r\n

Because the data is aimed to be sent in a series of chunks instead of the whole one, the normal Content-Length header is omitted.

Server Side Example

The following code snippet shows a Web API method transmitting a large JSON array. The Get method returns each object in Iterative Pattern which we have taken a look at before. A customized HTTP Message Handler will enable Chunked Transfer Encoding before return stream starts.

// Namespaces omitted

namespace WebApplication1.Controllers
{
    public class ValuesController : ApiController
    {
        [HttpGet]
        public IEnumerable<ReturnModel> Get()
        {
            // An example of returning large number of objects
            foreach (var i in Enumerable.Range(0, 10000))
                yield return new ReturnModel() { SequenceNumber = i, ID = Guid.NewGuid() };
        }
    }

    public class Handler : DelegatingHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, 
            CancellationToken cancellationToken)
        {
            var response = base.SendAsync(request, cancellationToken);
            
            response.Result.Headers.TransferEncodingChunked = true; // Here!
            
            return response;
        }
    }

    [DataContract]
    public class ReturnModel
    {
        [DataMember]
        public int SequenceNumber { get; set; }

        [DataMember]
        public Guid ID { get; set; }
    }
}

The following code snippet shows how to add our customized HTTP message handler to the collection.

// Namespaces omitted 

namespace WebApplication1
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services
            // Web API routes
            config.MapHttpAttributeRoutes();
            config.MessageHandlers.Add(new Handler());

            // Others omitted.
        }
    }
}

Client Side Example

The next code snippet demonstrates a console application consuming the JSON array from server. With JsonTextReader and JsonSerializer, the client application can start enumerating each object in the array without waiting for the whole JSON data to be transmitted.

// Namespaces omitted

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey(true);

            foreach (var value in GetValues())
                Console.WriteLine("{0}\t{1}", value.SequenceNumber, value.ID);

            Console.ReadKey(true);
        }

        static IEnumerable<ReturnModel> GetValues()
        {
            var serializer = new JsonSerializer();
            var client = new HttpClient();
            var header = new MediaTypeWithQualityHeaderValue("application/json");

            client.DefaultRequestHeaders.Accept.Add(header);

            // Note: port number might vary.
            using (var stream = client.GetStreamAsync
            ("http://localhost:63025/api/values").Result)
            using (var sr = new StreamReader(stream))
            using (var jr = new JsonTextReader(sr))
            {
                while (jr.Read())
                {
                    // Don't worry about commas.
                    // JSON reader will handle them for us.
                    if (jr.TokenType != JsonToken.StartArray && jr.TokenType != JsonToken.EndArray)
                        yield return serializer.Deserialize<ReturnModel>(jr);
                }
            }
        }
    }
}

You probably have noticed that there is nothing that the client application needs to do for Chunked Transfer Encoding. The reason is that by default, Chunked Transfer Encoding is expected to be accepted by clients. Since HttpClient has already handled it behind-the-scenes, we do not need to take extra action for the encoding.

References

History

  • 2017-05-05 Initial post
  • 2017-05-05 Added example download link

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here