Search code examples
c#streamasp.net-core-webapiminimal-apisjsonlines

How to return chunked text/plain content from Web API using minimal API


Using an ASP.NET Core 6 Web API written in C# with minimal API, I would like to return a stream of data without first loading the data into memory. In my case this is JSONL (JSON lines) data that is written by Apache Spark. JSONL is a text-based format.

This code below sets the Content-Type: application/json which is not correct for my use case. Setting this type then wraps the entire content with an array and adds escape backslash characters all through where there are quotes.

It should instead set Content-type: text/plain which would preserve the original formatting of the lines, and allow the consumers of this endpoint to stream and process one line at a time without loading the entire response body into memory on the client.

Is it possible to change this content-type while keeping the stream Transfer-Encoding: chunked and with it not parsing or modifying the line content I am reading from the .jsonl file?

app.MapGet("/stream/data", () =>
{
    async IAsyncEnumerable<string> Stream()
    {
        using (StreamReader file = new StreamReader(filePath))
        {
            while (!file.EndOfStream)
            {
                yield return await file.ReadLineAsync() ?? string.Empty;
            }
        }
    }

    return Stream();
});

Solution

  • You can set up a custom IResult that handles the line per line reading and writing to the response.

    public sealed class JsonLines : IResult
    {
        private readonly string _filePath;
    
        public JsonLines(string filePath)
            => _filePath = filePath;
    
        public async Task ExecuteAsync(HttpContext httpContext)
        {
            httpContext.Response.ContentType = "text/plain"; // Or "application/json"
    
            using var reader = new StreamReader(_filePath);
    
            while (!reader.EndOfStream)
            {
                var line = await reader.ReadLineAsync();
                if (line is not null)
                {
                    await httpContext.Response.WriteAsync(line);
                }
            }
        }
    }
    

    Your MapGet will look like below.

    app.MapGet("/stream/data", () => new JsonLines(@"c:\yourdatafile.json"));
    

    Fiddler shows that the expected headers are present, and that the response is chunked.

    enter image description here


    You might reconsider setting the Content-Type header (back) to application/json since ASP.NET Core will not touch upon the content anymore.