Click here to Skip to main content
15,885,948 members
Articles / Web Development / HTML

Large JSON Array Streaming in ASP.NET Web API

Rate me:
Please Sign up or sign in to vote.
4.95/5 (20 votes)
5 May 2017CPOL2 min read 96.5K   919   29   7
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.

C#
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.

C#
// 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.

C#
// 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.

C#
// 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)


Written By
Software Developer
Taiwan Taiwan
Back-end developer, English learner, drummer, game addict, Jazz fan, author of LINQ to A*

Comments and Discussions

 
GeneralMy vote of 5 Pin
gsubiran22-May-20 8:01
gsubiran22-May-20 8:01 
QuestionException handling Pin
Member 1468448710-Dec-19 0:30
Member 1468448710-Dec-19 0:30 
QuestionLarge JSON Pin
Abbathsin7-Aug-19 14:37
Abbathsin7-Aug-19 14:37 
QuestionIs this possible in ASP.NET Core? Pin
mblataric28-May-19 1:02
mblataric28-May-19 1:02 
QuestionNot Chunking in Client Pin
Member 40358104-May-18 3:39
Member 40358104-May-18 3:39 
AnswerRe: Not Chunking in Client Pin
Karay AKAR - Yapar29-Apr-19 16:23
Karay AKAR - Yapar29-Apr-19 16:23 
PraiseA small and very useful piece of software!! Pin
RaulMRG10-May-17 3:20
RaulMRG10-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.