File Uploads & Streaming Large Files

15 questions found

How do you accept a file upload in an ASP.NET Core controller action using IFormFile?

Beginner
An action parameter of type IFormFile automatically binds an uploaded file from a multipart/form-data request, giving access to its stream (OpenReadStream()), file name, content type, and length -- ASP.NET Core handles the multipart parsing automatically, letting you focus on what to do with the file's content.
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file) {
    if (file.Length == 0) return BadRequest("Empty file");
    using var stream = file.OpenReadStream();
    await _storageService.SaveAsync(file.FileName, stream);
    return Ok(new { file.FileName, file.Length });
}
Real-world example A document management API accepts PDF uploads via a simple IFormFile parameter, automatically getting the file's name, size, and content stream without manually parsing the multipart request body.

Common follow-ups: What's the maximum default upload size, and how do you configure it?;How do you accept multiple files in a single request?

Model Binding & Validation;Hosting Models: Kestrel IIS & Reverse Proxies

Why is buffering an entire large file upload in memory (via IFormFile) problematic, and how does streaming avoid this issue?

Intermediate
IFormFile by default buffers the entire uploaded file (potentially to memory or a temp disk file depending on size) before your action code even runs, which for very large files (video, large datasets) can cause significant memory pressure or exhaust server resources under concurrent uploads -- streaming instead processes the file's bytes incrementally as they arrive over the network, without ever holding the complete file in memory at once, using Request.Body directly with a multipart reader.
// Buffered approach: entire file loaded before action code runs (problematic for very large files)
[HttpPost]
public async Task<IActionResult> Upload(IFormFile file) { ... }

// Streaming approach: process incrementally without full buffering
[HttpPost]
[DisableFormValueModelBinding]
public async Task<IActionResult> UploadStreamed() {
    var reader = new MultipartReader(boundary, Request.Body);
    var section = await reader.ReadNextSectionAsync();
    // process section.Body as a stream, writing incrementally to storage
}
Real-world example A video upload service switches from IFormFile-based buffered uploads to true streaming, preventing memory spikes that previously caused occasional out-of-memory crashes when multiple large video uploads happened concurrently.

Common follow-ups: What is DisableFormValueModelBinding and why is it needed for true streaming?;At what file size does buffering actually become a practical problem?

Diagnostics & Performance;Memory Management & Garbage Collection

How would you implement true streaming file upload using MultipartReader to avoid ASP.NET Core's default buffering behavior entirely?

Advanced
Disable the default form value model binding (via [DisableFormValueModelBinding] and not using an IFormFile parameter at all), then manually read the multipart request using MultipartReader against Request.Body, processing each section's stream incrementally (writing directly to a destination stream like a file or blob storage) without ever materializing the complete file content in memory or a temp file simultaneously.
[HttpPost("stream-upload")]
[DisableFormValueModelBinding]
public async Task<IActionResult> StreamUpload() {
    var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType));
    var reader = new MultipartReader(boundary, Request.Body);
    var section = await reader.ReadNextSectionAsync();
    while (section != null) {
        var fileSection = section.AsFileSection();
        if (fileSection != null) {
            using var targetStream = File.Create(Path.Combine(_uploadPath, fileSection.FileName));
            await fileSection.FileStream.CopyToAsync(targetStream);
        }
        section = await reader.ReadNextSectionAsync();
    }
    return Ok();
}
Real-world example A large media platform accepting multi-gigabyte video uploads implements true MultipartReader-based streaming, keeping memory usage constant regardless of file size, versus the IFormFile approach which would have buffered gigabytes into memory or temp disk per concurrent upload.

Common follow-ups: What's the performance and complexity trade-off of manual streaming versus the simplicity of IFormFile?;How do you validate file type/size during streaming before the entire file has been read?

Diagnostics & Performance;Hosting Models: Kestrel IIS & Reverse Proxies

How do you configure and enforce a maximum request/file upload size in ASP.NET Core, and what's the default limit?

Intermediate
Kestrel's default MaxRequestBodySize is 30MB; configure it via KestrelServerOptions.Limits.MaxRequestBodySize globally, or override per-endpoint via [RequestSizeLimit(bytes)] on a controller action, or disable the limit entirely with [DisableRequestSizeLimit] for endpoints specifically designed to accept very large uploads (used carefully, combined with other safeguards against abuse).
builder.WebHost.ConfigureKestrel(options => {
    options.Limits.MaxRequestBodySize = 100_000_000;  // 100MB globally
});

[RequestSizeLimit(50_000_000)]  // 50MB override for this specific action
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file) { ... }
Real-world example A video-sharing platform sets a global 500MB Kestrel limit but overrides a specific low-risk thumbnail-upload endpoint down to a much stricter 5MB via [RequestSizeLimit], preventing that particular endpoint from being abused for oversized uploads.

Common follow-ups: How does this limit interact with a reverse proxy (like nginx) that might have its own separate size limit?;What HTTP status code is returned when the limit is exceeded?

Hosting Models: Kestrel IIS & Reverse Proxies;Rate Limiting

How would you implement resumable/chunked file uploads to support very large files over unreliable network connections?

Advanced
A chunked upload protocol splits a large file into smaller pieces uploaded via separate requests (each with a chunk index and total chunk count), with the server accumulating chunks (tracked via a temporary storage location keyed by an upload session ID) and assembling the complete file only once all chunks have arrived -- allowing an interrupted upload to resume from the last successfully received chunk rather than restarting from scratch, essential for large files over unreliable connections.
[HttpPost("upload-chunk")]
public async Task<IActionResult> UploadChunk(string uploadId, int chunkIndex, int totalChunks, IFormFile chunk) {
    var chunkPath = Path.Combine(_tempPath, uploadId, $"chunk-{chunkIndex}");
    using var stream = File.Create(chunkPath);
    await chunk.CopyToAsync(stream);

    if (await AllChunksReceivedAsync(uploadId, totalChunks)) {
        await AssembleChunksAsync(uploadId, totalChunks);
    }
    return Ok(new { chunkIndex, received = true });
}
Real-world example A cloud backup application uploading multi-gigabyte files over an unreliable mobile connection implements chunked, resumable uploads, letting a dropped connection resume from the last successfully uploaded chunk instead of restarting a multi-hour upload from zero.

Common follow-ups: How do you handle a client that never completes all chunks, leaving orphaned partial uploads?;How does this compare to using a cloud storage provider's native resumable upload API (like Azure Blob's block upload)?

Background Tasks & Hosted Services;Diagnostics & Performance

How do you stream a large file back to the client as a download response without loading the entire file into memory first?

Intermediate
Returning a FileStreamResult (via File(stream, contentType) or Results.Stream() in minimal APIs) with an open stream lets ASP.NET Core copy bytes from the stream directly to the response body incrementally as they're read, rather than requiring the entire file's bytes to be loaded into a byte array in memory before the response can begin sending.
[HttpGet("download/{id}")]
public async Task<IActionResult> Download(int id) {
    var stream = await _storageService.OpenReadStreamAsync(id);
    return File(stream, "application/octet-stream", "report.pdf");
}

// Minimal API equivalent
app.MapGet("/download/{id}", async (int id, IStorageService storage) => {
    var stream = await storage.OpenReadStreamAsync(id);
    return Results.Stream(stream, "application/octet-stream", "report.pdf");
});
Real-world example A reporting API serving multi-hundred-megabyte generated PDF reports streams them directly from blob storage to the client response, keeping server memory usage constant regardless of file size instead of loading each report fully into a byte array first.

Common follow-ups: What's the difference between FileStreamResult and FileContentResult in terms of memory usage?;How does this interact with range requests for resumable downloads?

Diagnostics & Performance;Content Negotiation & Output Formatters

How do you support HTTP range requests (partial content downloads) for large file streaming, enabling video seeking or resumable downloads?

Advanced
ASP.NET Core's File() result methods automatically support range requests (Range header) when the underlying stream supports seeking, returning 206 Partial Content responses for byte-range requests -- this is what enables video players to seek to arbitrary positions without downloading the entire file, and download managers to resume interrupted downloads from a specific byte offset rather than the beginning.
[HttpGet("video/{id}")]
public async Task<IActionResult> StreamVideo(int id) {
    var stream = await _storageService.OpenSeekableStreamAsync(id);
    return File(stream, "video/mp4", enableRangeProcessing: true);
}

// Client requesting: Range: bytes=1000000-2000000
// Response: 206 Partial Content with just that byte range
Real-world example A video streaming platform enables range processing on its video download endpoint, letting users seek to any point in a video (their player requests just that byte range) without downloading the entire file up to that point first.

Common follow-ups: What server-side stream capabilities are required for range processing to work (seekability)?;How do range requests interact with cloud blob storage's own native range-read support?

Diagnostics & Performance;Content Negotiation & Output Formatters

How do you validate an uploaded file's actual content type (not just its client-supplied extension or Content-Type header) to prevent malicious file uploads?

Intermediate
Since a client can freely lie about a file's Content-Type header or rename a file's extension, robust validation inspects the file's actual binary signature (magic bytes/file header) rather than trusting client-supplied metadata -- libraries like FileSignatures or manual magic-byte checking verify a file genuinely is what it claims to be before accepting it, an important defense against uploading disguised executable or script content.
public bool IsValidImageFile(Stream stream) {
    var buffer = new byte[8];
    stream.Read(buffer, 0, 8);
    stream.Position = 0;  // reset for later reading
    // PNG magic bytes: 89 50 4E 47 0D 0A 1A 0A
    return buffer[0] == 0x89 && buffer[1] == 0x50 && buffer[2] == 0x4E && buffer[3] == 0x47;
}
Real-world example A user profile picture upload feature rejects a malicious file disguised with a .jpg extension and image/jpeg Content-Type header but actually containing executable script content, caught by inspecting the file's genuine magic bytes rather than trusting client-supplied metadata.

Common follow-ups: What other security risks exist for file uploads beyond content type spoofing (path traversal, oversized files)?;How would you scan uploaded files for malware as an additional layer of defense?

Security Headers Antiforgery & CSRF Protection;Error Handling

How would you implement direct-to-cloud-storage uploads (bypassing your API server entirely) using pre-signed URLs, and why is this approach preferred for very large files?

Advanced
Rather than routing large file bytes through your API server (consuming its bandwidth, memory, and compute resources), your API generates a time-limited, pre-signed upload URL (via the cloud storage provider's SDK, like Azure Blob Storage's SAS tokens or AWS S3's presigned URLs) that the client uploads directly to, with your API only handling the lightweight metadata coordination -- dramatically reducing server load and improving upload performance for large files.
[HttpPost("request-upload-url")]
public IActionResult RequestUploadUrl(string fileName) {
    var blobClient = _containerClient.GetBlobClient(fileName);
    var sasUri = blobClient.GenerateSasUri(BlobSasPermissions.Write, DateTimeOffset.UtcNow.AddMinutes(15));
    return Ok(new { uploadUrl = sasUri.ToString() });
}
// Client then uploads the actual file bytes DIRECTLY to sasUri, bypassing the API server entirely
Real-world example A video platform handling multi-gigabyte uploads generates a short-lived pre-signed Azure Blob SAS URL for each upload request, letting clients upload directly to blob storage and completely avoiding routing massive file bytes through the API server's own bandwidth and compute.

Common follow-ups: How do you validate the uploaded file's properties (size, type) after a direct-to-storage upload, since your API never saw the actual bytes?;What are the security considerations for pre-signed URL expiration and scope?

Diagnostics & Performance;Security Headers Antiforgery & CSRF Protection

How do you accept and process multiple files uploaded in a single request?

Intermediate
An action parameter of type IFormFileCollection or List<IFormFile> automatically binds all files present in a multipart request with matching form field names, letting you iterate and process each uploaded file, useful for batch upload scenarios like a photo gallery accepting several images at once.
[HttpPost("upload-multiple")]
public async Task<IActionResult> UploadMultiple(List<IFormFile> files) {
    var results = new List<string>();
    foreach (var file in files) {
        if (file.Length > 0) {
            await _storageService.SaveAsync(file.FileName, file.OpenReadStream());
            results.Add(file.FileName);
        }
    }
    return Ok(new { uploadedFiles = results });
}
Real-world example A photo gallery feature lets users select and upload up to 10 images in a single form submission, bound automatically as a List<IFormFile> and processed in a single loop rather than requiring 10 separate upload requests.

Common follow-ups: How would you enforce a maximum number of files per upload request?;What happens if individual files within the batch have wildly different sizes -- does one large file block the others?

Model Binding & Validation;Rate Limiting

Showing 1–10 of 15