Skip to main content

File Storage

The File Storage feature lets your apps manage files and folders in the shared ArconQ file storage — uploading, downloading, listing, copying and deleting them. The same storage is available to operators through the File Storage page in the ArconQ UI.

The SDK exposes two APIs over the same storage:

  • IFileStorage — the recommended, path-based API. It works with files and folders using familiar remote paths (for example reports/2026/summary.csv) and validates those paths for you.
  • IBlobStorage — a lower-level, key-based API for working directly with storage objects (blobs) and prefixes. Use it only when you need raw object access; see Advanced: blob-level access.

Both services are registered automatically when you call AddArconQ(...), so you can inject either one directly into your services — no extra registration is required.

Breaking change in 1.3.0

The File Storage SDK was reworked in ArconQ 1.3.0. The previous IFileStorageBus interface, along with the FileEntry type and FileEntryType enum, has been removed and replaced by IFileStorage / IBlobStorage. Apps built against the old API will not compile until they are updated. See the Migrating to ArconQ 1.3.0 guide for the old-to-new mapping.

Remote paths​

All remote paths are relative to the file storage root and use / as the separator. Leading and trailing slashes are ignored, so reports/data.csv, /reports/data.csv and reports/data.csv/ all refer to the same file.

To keep files portable across operating systems, every path segment must be a valid file name on Linux, macOS and Windows. A path is rejected with an ArgumentException when it contains:

  • empty segments, or . / .. segments,
  • control characters or any of the \ < > : " | ? * characters,
  • a segment ending with a dot or a space,
  • a reserved Windows device name (for example CON, NUL, COM1).

Using IFileStorage​

Inject IFileStorage into a service and use its methods to manage files and folders. Every method accepts an optional CancellationToken.

public class ReportStorageService
{
private readonly IFileStorage _fileStorage;

public ReportStorageService(IFileStorage fileStorage)
{
_fileStorage = fileStorage;
}
}

Uploading files​

Upload a single file either from a local path or from a stream:

public async Task UploadFromLocalAsync(string localPath, string remotePath, CancellationToken cancellationToken)
{
// The local file name is NOT appended automatically — remotePath must include the file name.
await _fileStorage.UploadFileAsync(localPath, remotePath, cancellationToken);
}

public async Task UploadFromStreamAsync(Stream content, string remotePath, CancellationToken cancellationToken)
{
await _fileStorage.UploadFileAsync(content, remotePath, cancellationToken);
}

To upload a whole folder, use UploadDirectoryAsync. Only the contents of the local folder are uploaded, preserving the relative subfolder structure but not the folder name itself. For example, uploading C:/data/photos to backup places C:/data/photos/a.jpg at backup/a.jpg and C:/data/photos/vacation/b.jpg at backup/vacation/b.jpg.

public async Task UploadFolderAsync(string localFolder, string remoteFolder, CancellationToken cancellationToken)
{
await _fileStorage.UploadDirectoryAsync(localFolder, remoteFolder, cancellationToken);
}

Downloading files​

caution

Local paths are not permanent storage. Each time an app is restarted, a new pod is created in Kubernetes, and all local files are lost. If you need to keep results, save them to File Storage, a database, or another permanent store.

Download a single file to a local path, or to a stream:

public async Task DownloadToLocalAsync(string remotePath, string localPath, CancellationToken cancellationToken)
{
// Missing parent folders are created and an existing local file is overwritten.
await _fileStorage.DownloadFileAsync(remotePath, localPath, cancellationToken);
}

public async Task<Stream> DownloadToStreamAsync(string remotePath, CancellationToken cancellationToken)
{
return await _fileStorage.DownloadFileAsync(remotePath, cancellationToken);
}

Download all files under a folder with DownloadDirectoryAsync. The folder contents are placed directly into the local target folder without recreating the source folder name — for example, downloading docs (containing docs/reports/a.txt) to C:/backup produces C:/backup/reports/a.txt. Missing local folders are created.

public async Task DownloadFolderAsync(string remoteFolder, string localFolder, CancellationToken cancellationToken)
{
await _fileStorage.DownloadDirectoryAsync(remoteFolder, localFolder, cancellationToken);
}

Listing files​

ListFilesAsync returns all files under a path, including files in nested folders. Pass an empty string to list everything in the storage.

public async Task<List<FileResponse>> ListReportsAsync(CancellationToken cancellationToken)
{
return await _fileStorage.ListFilesAsync("reports", cancellationToken);
}

Each entry is a FileResponse record:

/// <summary>File entry metadata provided from file storage.</summary>
/// <param name="Path">Full path of a file.</param>
/// <param name="Size">Size of a file in bytes.</param>
/// <param name="LastModifiedOn">File last modification timestamp.</param>
public record FileResponse(string Path, long Size, DateTime LastModifiedOn);

Copying files​

CopyFileAsync copies a file to a new location within file storage. The source file name is not appended automatically — the destination path must include the new file name.

public async Task CopyAsync(string sourcePath, string destinationPath, CancellationToken cancellationToken)
{
await _fileStorage.CopyFileAsync(sourcePath, destinationPath, cancellationToken);
}

Deleting files and folders​

Delete a single file, or a folder together with all of its contents:

public async Task DeleteFileAsync(string remotePath, CancellationToken cancellationToken)
{
await _fileStorage.DeleteFileAsync(remotePath, cancellationToken);
}

public async Task DeleteFolderAsync(string remoteFolder, CancellationToken cancellationToken)
{
// Deletes the folder and everything inside it.
await _fileStorage.DeleteDirectoryAsync(remoteFolder, cancellationToken);
}

IFileStorage method reference​

MethodDescription
ListFilesAsync(string remoteDirectoryPath, …)Lists all files under the path, including files in nested folders. Pass an empty string to list everything.
DownloadFileAsync(string remoteFilePath, string localFilePath, …)Downloads a file to a local path; parent folders are created and an existing file is overwritten.
DownloadFileAsync(string remoteFilePath, …)Downloads a file and returns its content as a Stream.
DownloadDirectoryAsync(string remoteDirectoryPath, string localDirectoryPath, …)Downloads all files under a folder, preserving the subfolder structure.
UploadFileAsync(string localFilePath, string remoteFilePath, …)Uploads a local file to the remote path.
UploadFileAsync(Stream fileStream, string remoteFilePath, …)Uploads a stream to the remote path.
UploadDirectoryAsync(string localDirectoryPath, string remoteDirectoryPath, …)Uploads the contents of a local folder, preserving the subfolder structure.
DeleteFileAsync(string remoteFilePath, …)Deletes a single file.
DeleteDirectoryAsync(string remoteDirectoryPath, …)Deletes a folder and all of its contents.
CopyFileAsync(string remoteSourceFilePath, string remoteDestinationFilePath, …)Copies a file to a new location in file storage.

Every method takes a trailing CancellationToken cancellationToken = default (omitted above for brevity).

Advanced: blob-level access​

IBlobStorage exposes the underlying storage objects (blobs) directly. It works with keys and prefixes instead of validated file paths, and does not apply the path rules described above. Prefer IFileStorage for everyday file and folder handling; reach for IBlobStorage only when you need raw object access.

public class BlobService
{
private readonly IBlobStorage _blobStorage;

public BlobService(IBlobStorage blobStorage)
{
_blobStorage = blobStorage;
}

public async Task<List<BlobResponse>> ListAsync(string prefix, CancellationToken cancellationToken)
{
// Lists every entry whose key starts with the prefix; pass "" to list all entries.
return await _blobStorage.ListAsync(prefix, cancellationToken);
}
}
MethodDescription
ListAsync(string prefix, …)Lists all entries whose key starts with the prefix. Pass an empty string to list all entries.
DownloadAsync(string key, …)Downloads blob content as a Stream.
UploadAsync(Stream fileStream, string key, …)Uploads stream content to the given key.
DeleteAsync(string key, …)Deletes the blob at the given key.
CopyAsync(string sourceKey, string targetKey, …)Copies a blob to another key.

ListAsync returns BlobResponse records:

/// <summary>Blob metadata.</summary>
/// <param name="Key">Key in file storage.</param>
/// <param name="Size">Size in bytes.</param>
/// <param name="LastModifiedOn">Last modification timestamp.</param>
public record BlobResponse(string Key, long Size, DateTime LastModifiedOn);