Skip to main content

Download File Handler

This page provides complete examples for implementing the Download File backend handler. The downloadfile endpoint retrieves files from storage and streams them to the user's browser.

What is Download File?

downloadfile is the backend endpoint that:

  • Receives a file identifier to download
  • Retrieves the file from your storage system
  • Streams the file to the browser with proper headers
  • Handles large files efficiently

Complete Download File Implementation

Choose your backend language:

Controllers/FormsPublicController.cs
public class FileRequestDto
{
public string FileId { get; set; }
}

[HttpPost("downloadfile")]
[AllowAnonymous]
public async Task<IActionResult> DownloadFile([FromBody] FileRequestDto request)
{
string tenantId = GetCurrentTenantId();

var fileStorageService = GetFileStorageService();
if (fileStorageService == null)
throw new InvalidOperationException("File storage provider not configured.");

Stream fileStream = null;
string fileName = null;

if (!string.IsNullOrEmpty(request.FileId))
{
// Parse file identifier (format: "bucket-name:path/to/file.pdf")
string[] parts = request.FileId.Split(':');
string bucketName = parts[0];
string filePath = parts[1];

// Get file stream from storage
fileStream = await fileStorageService.GetFileStreamFromBucket(
tenantId,
filePath,
bucketName
);

fileName = Path.GetFileName(filePath);
}

if (fileStream == null || fileName == null)
{
return NotFound(new { error = "File not found" });
}

// Set response headers for file download
Response.ContentType = GetMimeType(fileName);
Response.Headers.Add(
"Content-Disposition",
$"attachment; filename*=UTF-8''{Uri.EscapeDataString(fileName)}"
);

// Stream file to response in chunks (memory efficient)
await fileStorageService.WriteFileInChunks(Response.Body, fileStream);

return new EmptyResult();
}

private string GetMimeType(string fileName)
{
var extension = Path.GetExtension(fileName).ToLowerInvariant();

return extension switch
{
".pdf" => "application/pdf",
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".doc" => "application/msword",
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".xls" => "application/vnd.ms-excel",
".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".zip" => "application/zip",
".txt" => "text/plain",
_ => "application/octet-stream"
};
}

Request Structure

{
"fileId": "bucket-name:tenant/forms/2026/01/27/document.pdf"
}

Key Fields:

  • fileId - The file identifier returned from the upload endpoint

Response

The response is a binary file stream with appropriate headers:

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename*=UTF-8''document.pdf
Content-Length: 1048576

[Binary file data]

Storage Implementation Examples

S3 Storage

C# - Get File Stream from S3
public async Task<Stream> GetFileStreamFromS3(
string tenantId,
string filePath,
string bucketName)
{
var s3Client = new AmazonS3Client(/* credentials */);

var getRequest = new GetObjectRequest
{
BucketName = bucketName,
Key = filePath
};

var response = await s3Client.GetObjectAsync(getRequest);

return response.ResponseStream;
}

public async Task WriteFileInChunks(Stream outputStream, Stream fileStream)
{
const int bufferSize = 81920; // 80KB chunks
byte[] buffer = new byte[bufferSize];
int bytesRead;

while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await outputStream.WriteAsync(buffer, 0, bytesRead);
await outputStream.FlushAsync();
}
}

Local File Storage

C# - Get File Stream from Local Storage
public async Task<Stream> GetFileStreamFromLocal(
string tenantId,
string filePath,
string bucketName)
{
var fullPath = Path.Combine(_uploadDirectory, bucketName, filePath);

if (!File.Exists(fullPath))
throw new FileNotFoundException("File not found", fullPath);

return new FileStream(fullPath, FileMode.Open, FileAccess.Read);
}

Azure Blob Storage

C# - Get File Stream from Azure Blob
public async Task<Stream> GetFileStreamFromAzure(
string tenantId,
string filePath,
string containerName)
{
var blobClient = new BlobContainerClient(
_connectionString,
containerName
);

var blob = blobClient.GetBlobClient(filePath);

if (!await blob.ExistsAsync())
throw new FileNotFoundException("Blob not found", filePath);

var download = await blob.DownloadAsync();

return download.Value.Content;
}

Security Considerations

Authorization Check

Verify the user has permission to download the file:

C# - Check Download Permission
private async Task<bool> CanUserDownloadFile(string userId, string fileId)
{
var fileRecord = await _context.Files
.Where(f => f.FileId == fileId)
.FirstOrDefaultAsync();

if (fileRecord == null)
return false;

// Check ownership or permission
return fileRecord.UserId == userId ||
fileRecord.IsPublic ||
await _authService.HasPermission(userId, "files.download");
}

Rate Limiting

Implement rate limiting for downloads:

C# - Apply Rate Limiting
[RateLimit(MaxRequests = 10, WindowSeconds = 60)]
public async Task<IActionResult> DownloadFile([FromBody] FileRequestDto request)
{
// Implementation...
}

Virus Scanning

Scan files before allowing download:

C# - Check Virus Scan Status
private async Task<bool> IsSafeToDownload(string fileId)
{
var scanResult = await _context.FileScanResults
.Where(f => f.FileId == fileId)
.OrderByDescending(f => f.ScannedAt)
.FirstOrDefaultAsync();

return scanResult?.IsClean ?? false;
}

Inline vs Download

Support both inline viewing and download:

C# - Support Inline and Attachment
[HttpPost("downloadfile")]
public async Task<IActionResult> DownloadFile(
[FromBody] FileRequestDto request,
[FromQuery] bool inline = false)
{
// ... get file stream ...

var disposition = inline
? $"inline; filename*=UTF-8''{Uri.EscapeDataString(fileName)}"
: $"attachment; filename*=UTF-8''{Uri.EscapeDataString(fileName)}";

Response.Headers.Add("Content-Disposition", disposition);

// ... stream file ...
}

Large File Handling

For very large files (> 10MB), use the Get Linked File By Chunks endpoint instead. It supports:

  • HTTP range requests for efficient streaming
  • Resumable downloads
  • Video/audio streaming
  • Better memory management
C# - Redirect Large Files to Chunks
// For files larger than 10MB, redirect to chunked endpoint
public async Task<IActionResult> DownloadFile([FromBody] FileRequestDto request)
{
var metadata = await GetFileMetadata(request.FileId);

if (metadata.Size > 10 * 1024 * 1024) // 10MB
{
return BadRequest(new {
error = "File too large for direct download",
message = "Please use getlinkedfilebychunks endpoint for files larger than 10MB"
});
}

// ... proceed with download for smaller files ...
}

Next Steps