Modern web applications often need to deliver large files — such as videos, PDFs, or CAD models — to users efficiently. Instead of loading entire files into memory, ASP.NET Core allows developers to stream files and handle HTTP range requests, enabling clients to download files partially or resume interrupted downloads. This approach saves memory, improves performance, and enhances the user experience.
1. Understanding File Streaming in ASP.NET Core
When you use traditional file download methods like File.ReadAllBytes(), the entire file is loaded into memory, which is inefficient for large files.
Streaming, on the other hand, sends data in chunks, allowing clients to start receiving content while the rest of the file is still being read.
Example: Basic File Streaming
[HttpGet("download/{fileName}")]
public async Task<IActionResult> DownloadFile(string fileName)
{
var filePath = Path.Combine("Files", fileName);
if (!System.IO.File.Exists(filePath))
return NotFound("File not found.");
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
return File(stream, "application/octet-stream", fileName);
}
Key Points
The file is not fully loaded into memory.
ASP.NET Core handles streaming automatically using FileStreamResult.
Ideal for large media files or document downloads.
2. Supporting Range Requests for Partial Downloads
Modern browsers and video players often request byte ranges instead of entire files to support:
You can manually implement HTTP range handling to support these cases.
Example: Range Request Implementation
[HttpGet("stream/{fileName}")]
public async Task<IActionResult> StreamFile(string fileName)
{
var filePath = Path.Combine("Files", fileName);
if (!System.IO.File.Exists(filePath))
return NotFound();
var fileInfo = new FileInfo(filePath);
var fileLength = fileInfo.Length;
var rangeHeader = Request.Headers["Range"].ToString();
if (string.IsNullOrEmpty(rangeHeader))
return PhysicalFile(filePath, "application/octet-stream", enableRangeProcessing: true);
// Parse range
var range = rangeHeader.Replace("bytes=", "").Split('-');
var start = long.Parse(range[0]);
var end = range.Length > 1 && !string.IsNullOrEmpty(range[1]) ? long.Parse(range[1]) : fileLength - 1;
var contentLength = end - start + 1;
Response.StatusCode = StatusCodes.Status206PartialContent;
Response.Headers.Add("Accept-Ranges", "bytes");
Response.Headers.Add("Content-Range", $"bytes {start}-{end}/{fileLength}");
Response.Headers.Add("Content-Length", contentLength.ToString());
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
fs.Seek(start, SeekOrigin.Begin);
var buffer = new byte[64 * 1024]; // 64KB buffer
long remaining = contentLength;
while (remaining > 0)
{
var count = (int)Math.Min(buffer.Length, remaining);
var read = await fs.ReadAsync(buffer, 0, count);
if (read == 0) break;
await Response.Body.WriteAsync(buffer.AsMemory(0, read));
remaining -= read;
}
return new EmptyResult();
}
What Happens Here
The API reads the Range header from the client request.
It calculates the byte segment to send.
The file is streamed incrementally, allowing pause/resume functionality.
3. Enabling Range Processing Automatically
ASP.NET Core provides built-in range processing for static or physical files:
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Append("Accept-Ranges", "bytes");
}
});
Alternatively, you can use PhysicalFile() or VirtualFile() with:
return PhysicalFile(filePath, "application/pdf", enableRangeProcessing: true);
This is ideal when you want a simple and efficient approach without manually parsing headers.
4. Real-World Use Cases
Video Streaming Platforms – Serve MP4 files efficiently using range-based streaming.
Document Viewers (PDF, DOCX) – Load only required file sections for faster rendering.
AutoCAD or 3D File Renderers – Fetch model data progressively for WebGL visualization.
Download Managers – Enable users to pause/resume downloads seamlessly.
5. Performance Optimization Tips
Use asynchronous file I/O (await fs.ReadAsync) to avoid blocking threads.
Keep buffer sizes between 32KB–128KB for optimal throughput.
Serve large files from Azure Blob Storage, AWS S3, or CDN when possible.
Cache metadata (file size, last modified) to reduce disk I/O.
Conclusion
Implementing file streaming and range requests in ASP.NET Core ensures scalability, improved user experience, and efficient resource utilization.
Whether you’re serving PDFs, videos, or large datasets, these techniques help you handle modern client demands—such as resumable downloads and media streaming—without overloading your server memory.
By combining ASP.NET Core’s built-in range processing with custom streaming logic, you can build a flexible, high-performance file delivery system that meets both enterprise and user needs.