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:
OriginalName: The name of the file when it was uploaded.ContentType: The MIME type (e.g.,application/pdf).SizeBytes: The physical size of the file.Hash: A SHA-256 hash calculated during upload to ensure data integrity and detect duplicates.StorageProvider: The cloud provider where the file physically resides (Local,Azure,AWS).StoragePath: The relative path, Blob Name, or S3 Key used to retrieve the file from the provider.
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).
- Local Storage: The chunks are appended to a
.tempfile on disk. - Azure Blob Storage: The chunks are streamed directly to Azure using
StageBlockAsync. - AWS S3: The chunks are streamed directly to AWS using
UploadPartAsync.
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.
- Configuration: Requires
AzureContainerNameandAzureConnectionString. - Behavior: Files are organized in virtual directories by year and month (e.g.,
tenant_my-app/2024/05/uuid.pdf).
AWS S3
Recommended for AWS-hosted environments.
- Configuration: Requires
AwsBucketName,AwsRegion, and standard AWS Access/Secret keys.
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:
- Link Generation: The engine generates a strong cryptographic token and inserts a record into
Struktural_Sys_DownloadLink. - Access Attempt: When the user clicks the link, the
DownloadControllerintercepts the request. - Validation: It verifies the
ExpirationDateand ensuresCurrentUsesis strictly less thanMaxUses. - Audit Logging: The requester's IP address and timestamp are permanently recorded in the
Struktural_Sys_DownloadLogtable. - On-The-Fly Decryption: If the file was encrypted at rest (e.g., it was uploaded to a
SecureFilefield), the controller utilizes a.NET CryptoStreamto 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:
- The service registers the requested file map in the server's ephemeral Memory Cache for 5 minutes.
- When the user accesses the download endpoint using the generated token, the server opens a
ZipArchivedirectly against theHttpResponse.Body. - 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.