File Upload and Processing in ASP.NET Core: Streaming, Validation, and Cloud Storage
A practical guide to streaming uploads, validating files securely, and storing them in the cloud with ASP.NET Core
Handling file uploads in ASP.NET Core goes far beyond accepting a file and saving it to disk. In this guide, we’ll walk through streaming large uploads efficiently, validating files securely, and integrating with cloud storage in a way that scales for real-world production applications.
File uploads seem simple at first. A user selects a file, clicks upload, and your application stores it somewhere. But in production systems, file handling quickly becomes one of the most sensitive and performance-heavy parts of your application.
Large files can consume memory. Malicious files can introduce security risks. Poor validation can open the door to exploitation. And local disk storage often doesn’t scale in cloud environments.
ASP.NET Core provides strong primitives for handling file uploads safely and efficiently. Combined with cloud storage providers like Azure Blob Storage or Amazon S3, you can build upload systems that are secure, scalable, and reliable.
In this guide, we’ll cover:
Streaming vs buffering
Secure file validation
Safe file storage patterns
Processing uploads
Cloud storage integration
Performance and scalability considerations
Let’s start with the fundamentals.
Understanding How File Uploads Work in ASP.NET Core
When a browser uploads a file, it sends it as part of a multipart/form-data request. ASP.NET Core processes that request and exposes uploaded files through the IFormFile interface.
According to the official Microsoft documentation on file uploads in ASP.NET Core, there are two primary approaches: buffered uploads and streaming uploads
https://learn.microsoft.com/aspnet/core/mvc/models/file-uploads
Buffered uploads load the entire file into memory or temporary storage before your code handles it. Streaming uploads process the file in chunks as it arrives.
For small files, buffering is acceptable. For large files or high-traffic systems, streaming is the safer and more scalable option.
Basic File Upload Example
Let’s begin with a simple controller-based example.
[HttpPost]
public async Task<IActionResult> Upload(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest(”No file selected.”);
var filePath = Path.Combine(”uploads”, Path.GetFileName(file.FileName));
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
return Ok(”File uploaded successfully.”);
}This works, but it’s not production-ready.
It trusts the file name. It doesn’t validate file type. It writes directly to disk. And it buffers the file in memory.
We need to improve this.
Streaming File Uploads for Large Files
When handling large files, you should avoid buffering them entirely in memory.
ASP.NET Core allows streaming using Request.Body or the MultipartReader class.
Here’s a simplified streaming approach:
[HttpPost]
[DisableFormValueModelBinding]
public async Task<IActionResult> UploadLargeFile()
{
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
70_000);
var reader = new MultipartReader(boundary, Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
if (ContentDispositionHeaderValue
.TryParse(section.ContentDisposition, out var contentDisposition))
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
var filePath = Path.Combine(”uploads”, Guid.NewGuid().ToString());
using var targetStream = System.IO.File.Create(filePath);
await section.Body.CopyToAsync(targetStream);
}
}
section = await reader.ReadNextSectionAsync();
}
return Ok();
}Streaming keeps memory usage low, which is critical for high-load APIs.
Microsoft’s Kestrel server, designed for high-performance workloads, handles streaming efficiently
https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel
If your application allows users to upload large videos or documents, streaming is not optional. It’s necessary.
Validating Uploaded Files Securely
Security is the most overlooked part of file uploads.
Never trust:
File names
File extensions
Content type headers
Attackers can rename malicious files to bypass simple validation checks.
A safer approach includes:
Limiting file size
Checking allowed extensions
Verifying content signatures
Generating safe server-side file names
Limiting File Size
You can configure limits globally:
builder.Services.Configure<FormOptions>(options =>
{
options.MultipartBodyLengthLimit = 50 * 1024 * 1024; // 50 MB
});Or per action:
[RequestSizeLimit(50 * 1024 * 1024)]This prevents memory exhaustion attacks.
Validating File Extensions
var permittedExtensions = new[] { “.jpg”, “.png”, “.pdf” };
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!permittedExtensions.Contains(extension))
{
return BadRequest(”Invalid file type.”);
}But extension checking alone is insufficient.
Validating File Signatures
For stronger protection, inspect file signatures (magic numbers).
For example, PDF files begin with %PDF.
byte[] header = new byte[4];
await file.OpenReadStream().ReadAsync(header, 0, 4);
if (header[0] != 0x25 || header[1] != 0x50)
{
return BadRequest(”Invalid PDF file.”);
}For enterprise systems, consider antivirus scanning before final storage.
Microsoft recommends scanning uploaded files before making them available
https://learn.microsoft.com/aspnet/core/security/anti-request-forgery
Generating Safe File Names
Never store files using user-provided names.
Instead:
var safeFileName = $”{Guid.NewGuid()}{extension}”;This avoids path traversal vulnerabilities and collisions.
You can still store the original name in a database for display purposes.
Processing Files After Upload
Often, uploading is just the first step.
Common processing tasks include:
Image resizing
PDF parsing
Virus scanning
Metadata extraction
Video transcoding
These operations should not block the HTTP request.
Instead, use background processing.
ASP.NET Core supports hosted background services:
public class FileProcessingService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Process queued files
await Task.Delay(1000, stoppingToken);
}
}
}Register it:
builder.Services.AddHostedService<FileProcessingService>();For larger systems, use message queues like Azure Service Bus or RabbitMQ.
Storing Files in Cloud Storage
Local disk storage works in development, but cloud-native applications should use object storage.
Azure Blob Storage is a natural fit for ASP.NET Core applications
https://learn.microsoft.com/azure/storage/blobs/storage-blobs-introduction
Install the package:
dotnet add package Azure.Storage.BlobsExample upload:
var blobClient = new BlobClient(connectionString, containerName, safeFileName);
await blobClient.UploadAsync(file.OpenReadStream());This provides:
Automatic scalability
Redundancy
CDN integration
Global availability
Amazon S3 offers similar capabilities
https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html
Direct-to-Cloud Uploads
For very large files, you can generate pre-signed URLs and allow clients to upload directly to cloud storage.
This avoids routing large files through your server entirely.
Azure supports SAS tokens
https://learn.microsoft.com/azure/storage/common/storage-sas-overview
This pattern reduces:
Server load
Bandwidth costs
Scaling complexity
Performance Considerations
File uploads impact:
Memory usage
Thread pool utilization
Disk I/O
Network throughput
Always:
Use async I/O
Avoid loading full files into memory
Configure request limits
Monitor upload endpoints separately
For high-scale APIs, consider separating upload services into their own microservice.
Handling Concurrent Uploads
High concurrency requires careful planning.
Monitor:
CPU
Memory
Disk
Network
Implement rate limiting using ASP.NET Core middleware:
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter(”upload”, limiterOptions =>
{
limiterOptions.PermitLimit = 10;
limiterOptions.Window = TimeSpan.FromMinutes(1);
});
});This prevents abuse and protects resources.
Logging and Monitoring
Track:
Upload failures
Validation errors
File size metrics
Processing duration
Use Application Insights for telemetry
https://learn.microsoft.com/azure/azure-monitor/app/app-insights-overview
Monitoring upload endpoints separately helps identify bottlenecks early.
Common Mistakes to Avoid
Saving files with original names
Storing files inside wwwroot
Trusting Content-Type headers
Blocking requests during processing
Ignoring size limits
Skipping antivirus scanning
Small mistakes in upload systems can become serious vulnerabilities.
Real-World Example: Profile Image Upload
A simple pattern:
Validate extension and size
Stream file
Store in Blob Storage
Save metadata in database
Return public URL
This keeps your application clean and scalable.
When to Use Streaming vs Buffered Uploads
Buffered uploads are acceptable when:
Files are small
Traffic is low
Simplicity is preferred
Streaming is preferred when:
Files exceed a few MB
Concurrent uploads are expected
Cloud-native scaling matters
Final Thoughts
File upload handling is one of those features that seems simple until it isn’t.
Done correctly, it improves scalability and security. Done carelessly, it becomes a performance bottleneck or security risk.
ASP.NET Core gives you the tools. Cloud storage platforms give you scalability. Combining both thoughtfully is what makes production-ready systems possible.
Join The Community
If you found this guide helpful, subscribe to ASP Today on Substack for practical, real-world ASP.NET Core insights. Join us in Substack Chat to connect with other developers building modern .NET applications.


