Skip to main content

File Download

The Download command tells the frontend to initiate a file download for the user. The actual file is streamed from your backend's /downloadfile endpoint.

When your handler calls Download(), the command is returned in the /runevent response. The SDK receives it and triggers the download in the browser.


Command structure

{
"command": "Download",
"args": {
"fileName": "report-2025.xlsx",
"filePath": "my-bucket:reports/2025/report.xlsx"
}
}
  • fileName — the filename shown to the user in the browser's download prompt.
  • filePath — the file identifier (bucket:path) that your /downloadfile endpoint uses to locate and stream the file.

C# implementation

public async Task Form_onClick(string widgetName)
{
if (widgetName == "exportbtn")
{
// Generate the export file
string fileId = await GenerateExcelReport(record.GetData());

// Trigger download in the browser
Download(fileId, "employee-export.xlsx");
}
}

Generating and downloading a file

private async Task<string> GenerateExcelReport(Dictionary<string, object> formData)
{
var employees = await _db.Employees
.Where(e => e.DepartmentId.ToString() == formData.GetString("departmentId"))
.ToListAsync();

byte[] excelBytes = await _reportService.GenerateExcel(employees);

string path = $"exports/{Guid.NewGuid()}/employees.xlsx";
string fileId = await _fileStorage.UploadFileAsync(
path, excelBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

return fileId;
}

Downloading an existing attachment

public async Task Form_onClick(string widgetName)
{
if (widgetName == "downloadattachmentbtn")
{
DataRecord record = (DataRecord)(await GetRecord())!;
await record.LoadRecord();

string? fileId = record.GetField("document")?.ToString();
string? fileName = record.GetField("documentName")?.ToString() ?? "document.pdf";

if (string.IsNullOrEmpty(fileId))
{
ShowMessage("warning", "No document attached.");
return;
}

Download(fileId, fileName);
}
}

How /downloadfile works

When the Download command is received, the SDK calls your /downloadfile endpoint:

[HttpPost("downloadfile")]
public async Task<IActionResult> DownloadFile([FromBody] FileRequestDto request)
{
string[] parts = request.FileId!.Split(':', 2);
string bucketName = parts[0];
string filePath = parts[1];
string fileName = Path.GetFileName(filePath);

Stream? fileStream = await _fileStorage.GetFileStreamAsync(filePath, bucketName);
if (fileStream == null)
return NotFound("File not found");

Response.ContentType = _fileStorage.GetMimeType(fileName);
Response.Headers.Append("Content-Disposition",
$"attachment; filename*=UTF-8''{Uri.EscapeDataString(fileName)}");

await _fileStorage.WriteInChunksAsync(Response.Body, fileStream);
return new EmptyResult();
}

The Content-Disposition: attachment header triggers the browser's save-file dialog.


Inline vs. attachment

If you want the browser to attempt to display the file (e.g. open a PDF in a new tab) instead of saving it, use the document previewer command instead. See Triggering a Document Previewer.