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 an 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 server's 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 👍