Skip to content
bitzorcas
中EN

Guide

iManage lifecycle and streaming

iManage upload, download, checkout, checkin, history, tenant clients, memory, concurrency, idempotency, errors, and audit boundaries.

Last updated

IIManageArchivePort hides the vendor SDK behind five document operations. It is a translation layer, not a document-management use case. There is no current Application service to guarantee tenant provenance, authorization, scanning, idempotency, audit, or reconciliation.

1. Operation lifecycle

UploadCheckoutCheckin new versionCheckin Stream.NullDownload copyQuery history

Uploaded

CheckedOut

Downloaded

History

This visual shows port operations, not a locally persisted state machine. iManage owns the actual state; the platform has no shadow aggregate.

2. Tenant client selection

Each operation calls and disposes IManageClientFactory.Create(tenantId). This enables tenant-instance routing, but TenantId remains ordinary caller input.

Trusted tenant wrapper
// ① Never copy TenantId from the incoming command.
string tenantId = currentUser.User.TenantId;
// ② The owner authorizes the business record and target folder.
await policy.AuthorizeUploadAsync(tenantId, command.CaseId, command.FolderId, ct);
// ③ Only a clean, bounded file enters the connector.
await using Stream stream = await files.OpenCleanReadAsync(command.FileAssetId, ct);
// ④ The operation result still needs audit, idempotency, and reconciliation handling.
Result<IManageDocumentDto> result = await archive.UploadDocumentAsync(
command.ToConnectorRequest(tenantId), stream, ct);

The exact request constructor should follow the source version. The architectural point is trusted tenant and stream provenance.

3. Upload boundary

The adapter validates basic tenant, folder, name, and extension fields. It does not validate stream non-null/readability, remaining length, maximum bytes, actual content type, extension allowlist, scan state, idempotency, or an unknown result after timeout.

A non-seekable stream is reported with size zero. A null stream fails while reading CanSeek. Owner code must prevalidate and carry an explicit content length.

4. Download memory model

The adapter copies the provider stream fully into an unbounded MemoryStream and returns it.

iManage stream

Unbounded MemoryStream

Result Stream

HTTP / owner

This decouples provider-client lifetime at the cost of process memory. There is no maximum, backpressure, or temporary object store. IO failures during CopyToAsync are also outside the IManageException catch.

GA can buffer bounded small files, stream while extending client lifetime, or stage into controlled storage. Every choice needs byte, timeout, concurrency, and ownership limits.

5. Download example

Bounded download response
// Authorize before issuing any provider read.
await policy.AuthorizeDownloadAsync(tenantId, documentId, version, ct);
// Map provider failure before taking ownership of the returned stream.
Result<Stream> downloaded =
await archive.DownloadDocumentAsync(tenantId, documentId, version, ct);
if (downloaded.IsFailure)
return Problem(downloaded.Error);
// The caller owns and must dispose the current MemoryStream.
await using Stream content = downloaded.Value!;
return await responseWriter.WriteAttachmentAsync(content, safeFileName, ct);

The response writer still needs a byte limit, audit, safe filename, and security headers.

6. Checkout concurrency

Checkout changes remote state. The request has no idempotency key, expected version, or local operation record. A timeout cannot distinguish “not executed” from “locked remotely.”

Provider conflicts are not normalized into already-owned, owned-by-other, or unknown-outcome categories. Reconcile state before retrying.

7. Checkin semantics

The port comment says a null stream releases the lock without a version. The adapter actually passes Stream.Null to SDK CheckinAsync. No repository contract test proves the provider interprets an empty stream that way.

Do not promise this behavior until verified. A safer contract separates UndoCheckout/ReleaseLock from CheckinNewVersion instead of encoding two commands through stream presence.

Checkin also lacks expected checked-out user/version, content hash, and idempotency key. The owner should persist operation ID, remote document ID, start time, and outcome.

8. Custom fields

Only Custom1 through Custom30 are recognized case-insensitively. Unknown keys are silently ignored, which can mislead callers into believing metadata was archived.

Use a typed or tenant-configured schema, reject unknown/duplicate keys, bound values, classify sensitive fields, and record mapping version.

9. Time mapping

Vendor DateTime values are mapped with new DateTimeOffset(value, TimeSpan.Zero). That assumes UTC; it can throw for Local kind and merely labels Unspecified values without evidence.

Verify the SDK contract and normalize explicitly. History order, audit, and SLA depend on correct instants.

10. Error taxonomy

Only IManageException is caught. The error code becomes ToolConnectors.iManage.ProviderFailed:{vendorCode}, and the raw message is exposed.

Dynamic codes break stable client handling; raw messages can leak details; IO, cancellation, and mapping failures escape. Prefer fixed categories such as Unavailable, ProviderUnauthorized, NotFound, Conflict, RateLimited, Timeout, UnknownOutcome, and ProviderFailed. Keep vendor code in restricted diagnostics.

11. Audit, idempotency, reconciliation

The adapter intentionally does not audit, but no owner Application layer currently exists. There is therefore no unified proof of who uploaded, checked out, or checked in which case document.

Reserve an operation record before mutations, use a business idempotency key, persist remote identity/version on success, mark timeout as Unknown, reconcile through detail/history, and link audit to the same operation ID.

12. Tests and operations

Contract tests should cover tenant routing; null/unreadable/non-seekable/large streams; malicious extensions; custom fields; DateTime kinds; provider error mapping; download disposal; cancellation; checkout conflicts; null checkin semantics; and unknown-outcome reconciliation.

Monitor call volume, latency, bytes, conflicts, limits, unknown outcomes, reconciliation age, credential failures, and tenant-client creation. Never log file content, filename, custom values, or tokens.

Release review must answer:

  • maximum file and per-tenant throughput;
  • who disposes provider and returned streams on interruption;
  • which job reconciles checkout/checkin timeout;
  • duplicate operation-ID behavior;
  • provider-history timezone;
  • credential rotation impact on in-flight clients;
  • authoritative retention and deletion owner;
  • sandbox and production SDK compatibility;
  • provider rate-limit policy;
  • support procedure for orphaned locks.

No mutation should be enabled until its reconciliation owner and retention period are named.

Terminal window
rg -n "UploadDocumentAsync|DownloadDocumentAsync|CheckoutAsync|CheckinAsync|Custom[0-9]" \
src/Platform/ToolConnectors -g '*.cs'

Back to Tool Connectors

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%