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:
- C# / .NET
- PHP
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"
};
}
controllers/FormsPublicController.php
<?php
class FormsPublicController {
public function downloadFile(Request $request) {
$fileId = $request->input('fileId');
if (empty($fileId)) {
return response()->json(['error' => 'File ID is required'], 400);
}
$tenantId = $this->getCurrentTenantId();
$fileStorageService = $this->getFileStorageService();
if (!$fileStorageService) {
throw new Exception('File storage provider not configured.');
}
// Parse file identifier (format: "bucket-name:path/to/file.pdf")
$parts = explode(':', $fileId, 2);
if (count($parts) !== 2) {
return response()->json(['error' => 'Invalid file ID format'], 400);
}
$bucketName = $parts[0];
$filePath = $parts[1];
// Get file stream from storage
try {
$fileStream = $fileStorageService->getFileStreamFromBucket(
$tenantId,
$filePath,
$bucketName
);
$fileName = basename($filePath);
$mimeType = $this->getMimeType($fileName);
// Set response headers
return response()->stream(
function() use ($fileStream) {
while (!feof($fileStream)) {
echo fread($fileStream, 8192);
flush();
}
fclose($fileStream);
},
200,
[
'Content-Type' => $mimeType,
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
'Cache-Control' => 'no-cache, no-store, must-revalidate',
'Pragma' => 'no-cache',
'Expires' => '0'
]
);
}
catch (Exception $e) {
Log::error("File download failed: {$e->getMessage()}");
return response()->json(['error' => 'File not found'], 404);
}
}
private function getMimeType($fileName) {
$extension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
$mimeTypes = [
'pdf' => 'application/pdf',
'jpg' => 'image/jpeg',
'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'
];
return $mimeTypes[$extension] ?? 'application/octet-stream';
}
private function getCurrentTenantId() {
return session('tenant_id') ?? 'default';
}
private function getFileStorageService() {
return app('file.storage');
}
}
?>
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
- Upload Files Handler - Upload new files
- Delete File Handler - Delete files
- Get Linked File Handler - Retrieve file data as Base64