Click here to Skip to main content
13,199,318 members (44,844 online)
Click here to Skip to main content
Add your own
alternative version

Stats

17.8K views
181 downloads
23 bookmarked
Posted 4 May 2017

Large JSON Array Streaming in ASP.NET Web API

Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Robert Vandenberg Huang
Software Developer
Taiwan Taiwan
Software developer, video game enthusiast, drummer and also a huge Jazz music fan. Have been working on software development since junior high school. Love sharing knowledge with people, learning new things and having interaction with developer community.

My Homepage at GitHub

You may also be interested in...

Comments and Discussions

 
PraiseA small and very useful piece of software!! Pin
RaulMRG10-May-17 3:20
memberRaulMRG10-May-17 3:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.171020.1 | Last Updated 5 May 2017
Article Copyright 2017 by Robert Vandenberg Huang
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid