Skip to main content
Technical ManualLooking for step-by-step instructions? In the User Manual: Attachments

Documents

The Documents module keeps the files attached to business records: scanned invoices, receipt photos and other PDFs or images. It replaces the legacy scanned-invoice blobs (BOOK-44), which accepted images only, compressed them into a column of five tables and copied them onto every row of a bill. Rendering documents to PDF (invoices, checks, forms) will join it with the CrestApps PDF module (U14).

ProjectsCrestApps.Almohaseb.Documents (module), CrestApps.Almohaseb.Documents.Core (record, index, rules, owners contract, service)
FeatureCrestApps.Almohaseb.Documents (Documents & attachments), enabled by both business recipes; depends on the foundation
CollectionDocuments

Attachments​

Attachment (OwnerType, OwnerId, FileName, ContentType, Length, Sha256, Path, UploadedUtc, UploadedBy) is a document in the Documents collection; AttachmentIndex (ItemId, OwnerType, OwnerId, UploadedUtc, indexed on the owner) lists a record's files. DocumentsMigrations (version 1) creates it.

The files are in IAttachmentFileStore, a FileSystemStore under the tenant's App_Data/Sites/{tenant}/Attachments folder: not the media library, so no file has a public URL. The path is {owner type}/{owner id}/{attachment id}{extension}, every segment reduced to letters, digits, ., - and _ (AttachmentRules.GetPath). A blob store can replace it by registering another IAttachmentFileStore.

AttachmentRules:

  • Sniff(header) recognizes PDF, JPEG, PNG, GIF, WebP and TIFF from the first 16 bytes. The browser's type and the file extension are ignored, so an HTML page named invoice.pdf is refused.
  • CleanFileName(name, type) drops folders, replaces control and reserved characters, keeps at most 120 characters and gives the extension of the real type.

IAttachmentService / AttachmentService: AddAsync(ownerType, ownerId, fileName, stream, user) refuses an unknown owner or record (UnknownOwner), reads at most AttachmentOptions.MaxFileBytes + 1 (default 10 MB; TooLarge), refuses an empty file (Empty) or another type (UnsupportedType) and a record with MaxFilesPerRecord files (default 20; TooManyFiles), hashes the bytes (SHA-256), writes the file and saves the record. ListAsync, GetAsync, OpenAsync and DeleteAsync (record and file) complete it.

Owners​

IAttachmentOwner (OwnerType, ExistsAsync(id), AuthorizeAsync(user, id, manage)) is how a module lets files be attached to its records. Each registers its owners in a startup with [RequireFeatures("CrestApps.Almohaseb.Documents")], so Documents knows none of them:

Owner typeModuleRecordView / manage
Purchases.BillPurchasesIPayablesService.GetBillAsyncViewAlmohasebPurchases / ManageAlmohasebPurchases
Sales.ServiceTicketSales (with Service tickets & invoices)IServiceTicketService.GetAsyncViewAlmohasebReceivables / ManageAlmohasebReceivables
Sales.OtherIncomeSalesIReceivablesService.GetOtherIncomeAsyncViewAlmohasebReceivables / ManageAlmohasebReceivables
Assets.FixedAssetAssetsthe latest AlmohasebFixedAsset content itemViewContent / EditContent on the item, so owner-only permissions apply

Panels​

The editors of bills, service tickets and other income show the attachments through IRecordPanelProvider (CrestApps.Almohaseb.Admin.UI.Panels): an editor collects RecordPanels.BuildAsync(providers, new RecordPanelContext(recordType, recordId, user, returnUrl)) for a saved record and displays the shapes after its form. AttachmentsPanelProvider returns the AlmohasebAttachments shape (AttachmentsPanelViewModel) when an owner is registered for the record type and the user may view; without Documents there are no providers and nothing is shown. A content item editor is a single form and cannot hold the panel's forms, so FixedAssetAttachmentsDisplayDriver adds an Attachments link to the standalone page instead.

Routes​

Route (under the admin prefix)Route nameMethodsPermission
Almohaseb/Attachments (ownerType, ownerId, returnUrl)AlmohasebAttachmentsGET: the record's panels on a pageThe owner's view
Almohaseb/Attachments/UploadAlmohasebAttachmentUploadPOST multipart (ownerType, ownerId, files, returnUrl; request limit 32 MB)The owner's manage
Almohaseb/Attachments/{id} (download)AlmohasebAttachmentOpenGET: the file inline (range requests) or as a download, with X-Content-Type-Options: nosniffThe owner's view
Almohaseb/Attachments/{id}/DeleteAlmohasebAttachmentDeletePOST (returnUrl)The owner's manage

Return URLs must be local.

Not yet​

  • Virus scanning of uploads, and cleanup of the files of deleted records.
  • Attachments on credit sales, payments, vendor payments and journal entries.

See also​