File & Archive Management

Struktural treats physical files (documents, images, audio) as first-class citizens. To ensure database performance remains optimal, files are never stored as binary blobs inside the relational database. Instead, they are routed to a dedicated Storage Provider, while the relational database only maintains a metadata pointer.

1. The Struktural_Sys_File Entity

Whenever a user uploads a file through the UI, or a file is generated via a Report, a record is created in the Struktural_Sys_File system table.

This table acts as the centralized ledger for all physical assets in the tenant. It stores:

When you create a business entity (e.g., Employee) and add a field of type Image or File, the engine automatically creates a Foreign Key pointing to Struktural_Sys_File.Id.

2. Chunked Multipart Uploads

To support the upload of massive files (e.g., GB-sized video assets) without overwhelming the Kestrel web server's RAM, the platform utilizes chunked streaming.

When the frontend initiates an upload to /api/system/files, it splits the file into smaller chunks (usually 2MB-5MB).

Once the final chunk is received, the server issues a commit command to the cloud provider to stitch the blocks together, calculates the final SHA-256 hash, and inserts the Struktural_Sys_File metadata record.

3. Storage Providers

Storage is configured per-tenant in the app-config.json file.

Local Disk (Default)

Ideal for development or single-node deployments. Files are stored in the Apps/{appId}/files/ directory relative to the host.

Azure Blob Storage

Recommended for Azure-hosted environments.

AWS S3

Recommended for AWS-hosted environments.


4. Secure Download Links (ISecureDownloadService)

In highly regulated environments (Healthcare, Finance), sharing static, permanent URLs to downloaded files is a security violation. Struktural solves this using ephemeral, time-bound Download Links.

By injecting the ISecureDownloadService into a C# script, you can generate a one-time-use link to share a file.

// Generate a link valid for 1 use, expiring in 24 hours
string url = await SecureDownloads.CreateLinkAsync(Entity.ConfidentialDocumentId, maxUses: 1, expirationDate: DateTime.UtcNow.AddHours(24));

How it works:

  1. Link Generation: The engine generates a strong cryptographic token and inserts a record into Struktural_Sys_DownloadLink.
  2. Access Attempt: When the user clicks the link, the DownloadController intercepts the request.
  3. Validation: It verifies the ExpirationDate and ensures CurrentUses is strictly less than MaxUses.
  4. Audit Logging: The requester's IP address and timestamp are permanently recorded in the Struktural_Sys_DownloadLog table.
  5. On-The-Fly Decryption: If the file was encrypted at rest (e.g., it was uploaded to a SecureFile field), the controller utilizes a .NET CryptoStream to decrypt the file chunk-by-chunk as it streams it back to the client, ensuring the server's RAM is never overloaded by holding the entire decrypted file.

5. Dynamic ZIP Archives (IArchiveService)

If a user needs to download multiple files at once (e.g., downloading an entire "Client Dossier" containing contracts, ID scans, and reports), packaging these files into a ZIP on the server disk would consume massive amounts of I/O and storage space.

The IArchiveService solves this by streaming the ZIP directly to the HTTP response.

var fileMap = new Dictionary<string, int>
{
    { "Contracts/Lease_2024.pdf", 104 },
    { "Identification/Passport.jpg", 105 }
};

// Returns a secure token for the dynamic download
string token = await Archives.RegisterDynamicZipAsync("Client_Dossier.zip", fileMap);

How it works:

  1. The service registers the requested file map in the server's ephemeral Memory Cache for 5 minutes.
  2. When the user accesses the download endpoint using the generated token, the server opens a ZipArchive directly against the HttpResponse.Body.
  3. The server fetches each physical file one by one from the Cloud Provider (Azure/AWS) and streams the bytes seamlessly through the ZIP compressor directly to the user's browser. Zero bytes are written to the server's local disk.