Start here. This is the direct spoken answer to practice first.
Why this question matters
Large responses change the resource boundary of a request. A design that materializes the whole payload can exhaust memory long before network throughput becomes the visible bottleneck.
I produce or open the content as a stream and let the server copy it to the response asynchronously instead of building one byte array or string. In ASP.NET Core a file or stream result can own that transfer. I set the media type and download name, propagate request cancellation, and make ownership clear so the stream and any temporary file are disposed after the response finishes or the client disconnects.