Skip to main content

Delete File Handler

This page provides complete examples for implementing the Delete File backend handler. The deletefile endpoint removes files from your storage system when users delete attachments from forms.

What is Delete File?

deletefile is the backend endpoint that:

  • Receives a file identifier to delete
  • Removes the file from your storage system
  • Returns confirmation of deletion success
  • Handles cleanup of associated metadata

Complete Delete File Implementation

Choose your backend language:

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

[HttpPost("deletefile")]
[AllowAnonymous]
public async Task<IActionResult> DeleteFile([FromBody] FileRequestDto request)
{
var fileStorageService = GetFileStorageService();
if (fileStorageService == null)
throw new InvalidOperationException("File storage provider not configured.");

bool result = false;
var response = new HandlerResponse();

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];

// Delete file from storage
if (await fileStorageService.DeleteFileFromBucket(filePath, bucketName))
{
// Update response to reflect successful deletion
response.Set(HandlerResponse.LinkedFiles, new List<string>());
result = true;

_logger.LogInformation(
"File deleted successfully: {FileId}",
request.FileId
);
}
else
{
_logger.LogWarning(
"Failed to delete file: {FileId}",
request.FileId
);
}
}

response.Set(HandlerResponse.LinkedFileDeleteSuccess, result);
return Ok(await response.Get());
}

Request Structure

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

Key Fields:

  • fileId - The file identifier returned from the upload endpoint

Response Structure

{
"linkedFiles": [],
"linkedFileDeleteSuccess": true
}

Response Fields:

  • linkedFileDeleteSuccess - Boolean indicating if deletion was successful
  • linkedFiles - Empty array after successful deletion

Storage Implementation Examples

S3 Storage

C# - Delete File from S3
public async Task<bool> DeleteFromS3(string filePath, string bucketName)
{
try
{
var s3Client = new AmazonS3Client(/* credentials */);

var deleteRequest = new DeleteObjectRequest
{
BucketName = bucketName,
Key = filePath
};

var response = await s3Client.DeleteObjectAsync(deleteRequest);

return response.HttpStatusCode == System.Net.HttpStatusCode.NoContent;
}
catch (AmazonS3Exception ex)
{
_logger.LogError(ex, "S3 delete failed for {FilePath}", filePath);
return false;
}
}

Local File Storage

C# - Delete File from Local Storage
public Task<bool> DeleteFromLocal(string filePath, string bucketName)
{
try
{
var fullPath = Path.Combine(_uploadDirectory, bucketName, filePath);

if (File.Exists(fullPath))
{
File.Delete(fullPath);
return Task.FromResult(true);
}

return Task.FromResult(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Local file delete failed for {FilePath}", filePath);
return Task.FromResult(false);
}
}

Azure Blob Storage

C# - Delete File from Azure Blob Storage
public async Task<bool> DeleteFromAzure(string filePath, string containerName)
{
try
{
var blobClient = new BlobContainerClient(
_connectionString,
containerName
);

var blob = blobClient.GetBlobClient(filePath);

var response = await blob.DeleteIfExistsAsync();

return response.Value;
}
catch (Exception ex)
{
_logger.LogError(ex, "Azure blob delete failed for {FilePath}", filePath);
return false;
}
}

Security Considerations

Authorization Check

Always verify the user has permission to delete the file:

C# - Check Delete Permission
private async Task<bool> CanUserDeleteFile(string userId, string fileId)
{
// Check if file belongs to user's forms/records
var fileRecord = await _context.Files
.Where(f => f.FileId == fileId)
.FirstOrDefaultAsync();

if (fileRecord == null)
return false;

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

Soft Delete Option

Consider implementing soft delete instead of hard delete:

C# - Soft Delete Implementation
public async Task<bool> SoftDeleteFile(string fileId)
{
var fileRecord = await _context.Files
.Where(f => f.FileId == fileId)
.FirstOrDefaultAsync();

if (fileRecord == null)
return false;

fileRecord.DeletedAt = DateTime.UtcNow;
fileRecord.DeletedBy = GetCurrentUserId();

await _context.SaveChangesAsync();

return true;
}

Cleanup Associated Metadata

Remember to clean up database records:

C# - Cleanup File Metadata
private async Task CleanupFileMetadata(string fileId)
{
// Remove file reference from database
var fileRecord = await _context.Files
.Where(f => f.FileId == fileId)
.FirstOrDefaultAsync();

if (fileRecord != null)
{
_context.Files.Remove(fileRecord);
await _context.SaveChangesAsync();
}
}

Error Handling

Handle common deletion errors:

C# - Complete Error Handling Example
public async Task<IActionResult> DeleteFile([FromBody] FileRequestDto request)
{
try
{
if (string.IsNullOrEmpty(request.FileId))
{
return BadRequest(new {
linkedFileDeleteSuccess = false,
error = "File ID is required"
});
}

// Check authorization
if (!await CanUserDeleteFile(GetCurrentUserId(), request.FileId))
{
return Unauthorized(new {
linkedFileDeleteSuccess = false,
error = "You don't have permission to delete this file"
});
}

// Perform deletion
bool deleted = await DeleteFileFromStorage(request.FileId);

if (deleted)
{
await CleanupFileMetadata(request.FileId);
}

return Ok(new {
linkedFiles = new List<string>(),
linkedFileDeleteSuccess = deleted
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error deleting file");
return StatusCode(500, new {
linkedFileDeleteSuccess = false,
error = "Failed to delete file"
});
}
}

Next Steps