Isaac.

Efficient File Uploads

Handle file uploads securely and efficiently.

By EMEPublished: February 20, 2025
file uploadsstreamingvalidationsecurity

A Simple Analogy

File uploads are like mail delivery. Check packages at the door, validate contents, store safely.


Why Optimization?

  • Performance: Don't buffer entire file in memory
  • Security: Validate file types and size
  • UX: Progress tracking for large files
  • Reliability: Handle failures gracefully
  • Cost: Minimize bandwidth usage

Basic Upload

[HttpPost("upload")]
public async Task<IActionResult> UploadFile(IFormFile file)
{
    if (file == null || file.Length == 0)
        return BadRequest("File is required");
    
    if (file.Length > 10 * 1024 * 1024)  // 10MB
        return BadRequest("File too large");
    
    var allowedExtensions = new[] { ".pdf", ".docx", ".xlsx" };
    var fileExtension = Path.GetExtension(file.FileName).ToLower();
    
    if (!allowedExtensions.Contains(fileExtension))
        return BadRequest("Invalid file type");
    
    var filename = Path.Combine(_uploadPath, Guid.NewGuid() + fileExtension);
    
    using (var stream = System.IO.File.Create(filename))
    {
        await file.CopyToAsync(stream);
    }
    
    return Ok(new { filename });
}

Streaming Upload

[HttpPost("stream-upload")]
public async Task<IActionResult> StreamUpload()
{
    var boundary = GetBoundary(Request.ContentType);
    var reader = new MultipartReader(boundary, Request.Body);
    
    var section = await reader.ReadNextSectionAsync();
    
    while (section != null)
    {
        var hasContentDispositionHeader = 
            ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, 
                out var contentDisposition);
        
        if (hasContentDispositionHeader && contentDisposition.DispositionType.Equals("form-data"))
        {
            if (!section.ContentType.StartsWith("image/"))
            {
                section = await reader.ReadNextSectionAsync();
                continue;
            }
            
            var filename = contentDisposition.FileName.Value;
            var filepath = Path.Combine(_uploadPath, Guid.NewGuid() + Path.GetExtension(filename));
            
            using (var targetStream = System.IO.File.Create(filepath))
            {
                await section.Body.CopyToAsync(targetStream);
            }
        }
        
        section = await reader.ReadNextSectionAsync();
    }
    
    return Ok();
}

Progress Tracking

const input = document.querySelector('input[type="file"]');
const progressBar = document.querySelector('progress');

input.addEventListener('change', async (e) => {
  const file = e.target.files[0];
  const formData = new FormData();
  formData.append('file', file);
  
  const xhr = new XMLHttpRequest();
  
  xhr.upload.addEventListener('progress', (event) => {
    if (event.lengthComputable) {
      const percent = (event.loaded / event.total) * 100;
      progressBar.value = percent;
    }
  });
  
  xhr.addEventListener('load', () => {
    console.log('Upload complete');
  });
  
  xhr.open('POST', '/upload');
  xhr.send(formData);
});

Best Practices

  1. Validate: Check type, size, content
  2. Rename: Use GUIDs to prevent conflicts
  3. Stream: Don't load entire file into memory
  4. Sanitize: Clean filenames
  5. Quarantine: Scan for malware

Related Concepts

  • Chunked uploads
  • Resumable uploads
  • Progress tracking
  • Cloud storage

Summary

Handle file uploads securely with validation, streaming, and progress tracking. Store safely with sanitized names.