ASP.NET Core  

Efficient File Streaming and Range Requests in ASP.NET Core APIs

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:

  • Resumable downloads

  • Media streaming (e.g., MP4 playback)

  • Efficient caching

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

  1. Video Streaming Platforms – Serve MP4 files efficiently using range-based streaming.

  2. Document Viewers (PDF, DOCX) – Load only required file sections for faster rendering.

  3. AutoCAD or 3D File Renderers – Fetch model data progressively for WebGL visualization.

  4. 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.