Skip to main content

File Upload Events

File upload events handle file uploads through form widgets or datatable upload buttons. These events use multipart form data instead of JSON.

Event Types

EventTriggered WhenUpload Type
onFileUploadUser uploads files through a file widgetStandard upload
onFileUploadCreateDocumentEventUser uploads files through datatable upload buttonDatatable upload
Separate Upload Endpoint

File uploads require a separate endpoint that accepts multipart form data, not the regular runEvent endpoint. Configure this in your FormHostProvider settings.

File Upload Request Structure

Unlike other events, file uploads send multipart form data:

  • files - Array of uploaded files
  • widgetName - Name of the widget
  • widgetEvent - Event type (onFileUploadCreateDocumentEvent)
  • formData - Current form data as JSON string
  • widgetContext - Additional context as JSON string
  • fieldName - Name of the field/widget
  • uploadMode - Mode of upload ("single", "file", "container")

Standard File Upload Widget

Upload Endpoint Implementation

Controllers/FormsPublicController.cs
[HttpPost("uploadfiles")]
[AllowAnonymous]
public async Task<IActionResult> UploadFiles(
[FromForm] IFormFileCollection files,
[FromForm] string widgetName,
[FromForm] string widgetEvent,
[FromForm] string formData,
[FromForm] string widgetContext,
[FromForm] string fieldName)
{
var uploadedFiles = new List<Dictionary<string, object>>();
var formDataDict = JsonSerializer.Deserialize<Dictionary<string, object>>(formData);
var feCommands = new List<object>();

try
{
foreach (var file in files)
{
// Validate file
if (file.Length > 10 * 1024 * 1024) // 10MB limit
{
feCommands.Add(new
{
command = "ShowMessage",
args = new
{
type = "error",
message = $"File {file.FileName} exceeds 10MB limit"
}
});
continue;
}

// Generate unique file name
var fileExtension = Path.GetExtension(file.FileName);
var uniqueFileName = $"{Guid.NewGuid()}{fileExtension}";
var uploadPath = Path.Combine("uploads", DateTime.Now.ToString("yyyy-MM-dd"));

// Create directory if it doesn't exist
Directory.CreateDirectory(uploadPath);

var filePath = Path.Combine(uploadPath, uniqueFileName);

// Save file to disk
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}

// Save file metadata to database
var fileRecord = new UploadedFile
{
Id = Guid.NewGuid(),
OriginalFileName = file.FileName,
StoredFileName = uniqueFileName,
FilePath = filePath,
FileSize = file.Length,
ContentType = file.ContentType,
UploadedAt = DateTime.UtcNow,
UploadedBy = formDataDict.GetValueOrDefault("userId", "").ToString()
};

await _dbContext.UploadedFiles.AddAsync(fileRecord);

uploadedFiles.Add(new Dictionary<string, object>
{
{ "_id", fileRecord.Id.ToString() },
{ "fileName", file.FileName },
{ "fileSize", file.Length },
{ "uploadedAt", fileRecord.UploadedAt.ToString("yyyy-MM-dd HH:mm:ss") }
});
}

await _dbContext.SaveChangesAsync();

// Show success message
feCommands.Add(new
{
command = "ShowMessage",
args = new
{
type = "success",
message = $"{files.Count} file(s) uploaded successfully"
}
});

// Reload widget if it's a datatable
if (widgetEvent == "onFileUploadCreateDocumentEvent")
{
feCommands.Add(new
{
command = "ReloadWidget",
args = new { widgetName }
});
}

return Ok(new
{
success = true,
uploadedFiles = uploadedFiles,
FECommand = feCommands
});
}
catch (Exception ex)
{
return Ok(new
{
success = false,
error = ex.Message,
FECommand = new[]
{
new
{
command = "ShowMessage",
args = new
{
type = "error",
message = $"Upload failed: {ex.Message}"
}
}
}
});
}
}

Upload to Cloud Storage (S3, Azure, etc.)

AWS S3 Example

using Amazon.S3;
using Amazon.S3.Transfer;

[HttpPost("uploadfiles")]
public async Task<IActionResult> UploadFiles([FromForm] IFormFileCollection files)
{
var uploadedFiles = new List<Dictionary<string, object>>();
var feCommands = new List<object>();

// Initialize S3 client
var s3Client = new AmazonS3Client(
_configuration["AWS:AccessKey"],
_configuration["AWS:SecretKey"],
Amazon.RegionEndpoint.USEast1
);

var transferUtility = new TransferUtility(s3Client);
var bucketName = _configuration["AWS:BucketName"];

foreach (var file in files)
{
try
{
// Generate S3 key
var s3Key = $"uploads/{DateTime.UtcNow:yyyy-MM-dd}/{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";

// Upload to S3
using (var stream = file.OpenReadStream())
{
await transferUtility.UploadAsync(stream, bucketName, s3Key);
}

// Save metadata to database
var fileRecord = new UploadedFile
{
Id = Guid.NewGuid(),
OriginalFileName = file.FileName,
S3Key = s3Key,
FileSize = file.Length,
ContentType = file.ContentType,
UploadedAt = DateTime.UtcNow
};

await _dbContext.UploadedFiles.AddAsync(fileRecord);

uploadedFiles.Add(new Dictionary<string, object>
{
{ "_id", fileRecord.Id.ToString() },
{ "fileName", file.FileName },
{ "fileSize", file.Length },
{ "s3Url", $"https://{bucketName}.s3.amazonaws.com/{s3Key}" }
});
}
catch (Exception ex)
{
feCommands.Add(new
{
command = "ShowMessage",
args = new { type = "error", message = $"Failed to upload {file.FileName}: {ex.Message}" }
});
}
}

await _dbContext.SaveChangesAsync();

feCommands.Add(new
{
command = "ShowMessage",
args = new { type = "success", message = $"{uploadedFiles.Count} file(s) uploaded to S3" }
});

return Ok(new { success = true, uploadedFiles, FECommand = feCommands });
}

File Validation

Validate File Types and Sizes

private bool ValidateFile(IFormFile file, out string errorMessage)
{
errorMessage = null;

// Check file size (10MB max)
if (file.Length > 10 * 1024 * 1024)
{
errorMessage = $"{file.FileName} exceeds 10MB limit";
return false;
}

// Check file type
var allowedExtensions = new[] { ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".jpg", ".jpeg", ".png" };
var extension = Path.GetExtension(file.FileName).ToLower();

if (!allowedExtensions.Contains(extension))
{
errorMessage = $"{file.FileName} has invalid file type. Allowed: {string.Join(", ", allowedExtensions)}";
return false;
}

// Check MIME type
var allowedMimeTypes = new[]
{
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.ms-excel",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"image/jpeg",
"image/png"
};

if (!allowedMimeTypes.Contains(file.ContentType))
{
errorMessage = $"{file.FileName} has invalid MIME type: {file.ContentType}";
return false;
}

return true;
}

// Usage in upload handler
foreach (var file in files)
{
if (!ValidateFile(file, out var errorMessage))
{
feCommands.Add(new
{
command = "ShowMessage",
args = new { type = "error", message = errorMessage }
});
continue;
}

// Process valid file...
}

Best Practices

  1. Validate all files - Check file size, type, and MIME type
  2. Use unique file names - Prevent file name collisions
  3. Store metadata - Save file information in database for tracking
  4. Handle errors gracefully - Provide clear error messages for each file
  5. Limit file sizes - Set reasonable upload limits
  6. Scan for viruses - Integrate virus scanning for uploaded files
  7. Use cloud storage - For scalability and reliability

Next Steps