Stream large content from ASP.NET Core to avoid OutOfMemoryException

I had to implement an endpoint in ASP.NET Core, fetching a large amount of data and returning it to the client. Just to end up in a System.OutOfMemoryException on the server. With just a few changed code lines and the built in support for streaming content in ASP.NET Core, I was able to fix the issue. Stay tuned to learn how.

Ever needed to return a large response from a web server? Depending on your hardware, you can quickly end up in a situation like mine, where you use all of the servers memory to build the return type to the client, before actually returning it.

Let me demonstrate with a simple example. I'll start with a simple view, implemented directly in the /Home/Index.cshtml file:

<div class="text-center">
    <script>
        function Fetch() {
            $.get("/home/values", function (data) {
                console.log(data);
            });
        }
    </script>
    <button class="btn btn-primary btn-lg" onclick="Fetch()">Fetch</button><br />
</div>

Good old HTML with inline JavaScript as it would have looked 20 years ago. The client isn't important here, other than to provide us with a button to call the endpoint implemented in the HomeController:

public IEnumerable<string> Values()
{
    var random = new Random();
    var result = new List<string>();
    for (var i = 0; i < 100000000; i++)
    {
        result.Add(random.Next(100000, 999999).ToString());
    }
    return result;
}

The endpoint builds a list of 100,000,000 random strings and returns it to the client. As you probably figured out already, running the code causes the following exception to happen:

We are trying to build a very large data structure in memory, before returning it to the client. To be able to compare with the solution presented in a moment, here's how the call looks in Developer Tools on the client:

The response is pending and finally fails with a status code 500 (caused by the OutOfMemoryException.

The solution is to use the yield keyword built into C#. Let's refactor the endpoint to use yield:

public IEnumerable<string> Values()
{
    var random = new Random();
    for (var i = 0; i < 100000000; i++)
    {
        yield return random.Next(100000, 999999).ToString();
    }
}

The code is pretty similar to the previous version. But now we no longer build a list in memory, but rather yield return each random string to the client. Streaming content like this is visible if we inspect the request in Developer Tools:

As shown in the video, the request returns a 200 response as soon as I click the Fetch button and the server starts streaming a lot of MBs to the client. Simple as that 👍

elmah.io: Error logging and Uptime Monitoring for your web apps

This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.

See how we can help you monitor your website for crashes Monitor your website