How the Viewer Works

The document request lifecycle

Doconut renders documents as paginated images served through ASP.NET Core middleware. Understanding the lifecycle — open, token, page requests, close — explains almost every behavior you will observe, including the error messages.

The three moving parts

  • Viewer — the public service you inject. It opens documents and returns session tokens.
  • The document session — a server-side object holding the loaded document, keyed by a token in IMemoryCache.
  • The Doconut middleware — added by UseDoconut(); answers every request the browser widget makes (pages, thumbnails, search, annotations, …), always authenticated by the token.

Viewer is stateless — by design

Viewer is sealed, holds no per-request document state, and deliberately does not implement IDisposable. Sessions live independently in the session manager and are cleaned up by cache expiration or an explicit CloseDocument(token).

You no longer construct it. AddDoconut() registers it as a transient service; inject it wherever you need it:

csharp
app.MapPost("/api/open", async (string fileName, Viewer viewer) =>
{
    var token = await viewer.OpenDocumentAsync($"files/{fileName}");
    return Results.Content(token, "text/plain");
});

In an MVC application, take it as a constructor parameter; in a Razor view, use @inject Doconut.Viewer Viewer. Application-owned caches of Viewer instances are no longer needed — and no longer correct, because they hold nothing worth caching.

What happens inside OpenDocumentAsync

  1. License gate. A rejected or version-expired license (blacklisted, tampered, or a build outside the license's update window) throws a LicenseException immediately, with the rejection reason as the message — opening never silently degrades for an invalid (as opposed to absent) license. A calendar-expired Temporary or subscription license is the exception: it does not throw — it degrades to a watermark.
  2. Session creation. The viewer factory picks the right format viewer for the file extension and loads the document (see Rendering Pipeline). The session is stored in IMemoryCache under a fresh GUID token with a sliding expirationDocOptions.TimeOut minutes, default 60. Every page request resets the clock.
  3. Security registration. With UnsafeMode = false (the default), the token is bound to the caller's ASP.NET session: a secure-{token} marker is written into the session, so only the browser session that opened the document can request its pages.
  4. The token is returned. It is the single credential for everything that follows.

There is no separate warm-up step. The previous library exposed InitCache() for that purpose; it no longer exists, and nothing replaces it.

The three overloads differ only in input: a file path, a file path plus a per-format config (PdfConfig, WordConfig, …), or a Stream plus a FileInfo whose extension drives format detection.

How the widget gets pages

The client widget calls the Doconut middleware with the token in the query string. What the middleware does depends on the request:

QueryPurpose
?token=…&page=NRendered page image (PNG)
?token=…&page=N&thumb=1Thumbnail
?token=…&zoom=…Zoomed page rendering
?token=…&search=termFull-text search (license-gated)
?token=…&bookmarksDocument outline/bookmarks
?token=…&copy / &showlinks / &fileFormatText copy, hyperlinks, and format info
?token=…&metaDICOM technical metadata; returns 501 for a DICOM session on this package
?token=…&action=rotate/flip/closePage actions and explicit close
?token=…&AnnSave=… / &AnnLoadSave/load annotations

Every one of these paths validates first:

  • No token → the middleware returns 404 (or a version banner when ShowDoconutInfo = true).
  • Unknown or expired token → an error image with Document session not found. Please re-open document.
  • Session middleware missing (with UnsafeMode = false) → HTTP 500 with Session middleware not configured. Call UseSession() before UseDoconut().
  • Token opened by a different browser session → an error image with You Are Not Authorized To View This Page.

Where the middleware is mounted

UseDoconut() answers requests that carry a token, wherever you mount it. The reference application keeps the historic DocImage.axd request shape on a MapWhen branch, so an existing client keeps calling the URL it always did. Whatever you choose, the client's BasePath must agree with it — see ViewerConfig.

Serving pages from shared storage instead of the opening node is a different arrangement entirely; see Distributed Deployments.

Closing a document

csharp
viewer.CloseDocument(token);

CloseDocument removes the session from the cache (which disposes the underlying document engine and frees its memory immediately), deletes the secure-{token} marker, and revokes the access grant. Calling it is optional — sliding expiration does the same cleanup automatically — but for large documents it is the polite way to release memory the moment the user is done.

It is called on the injected Viewer. The previous pattern — constructing a short-lived Viewer in a using block purely to close a token — has no equivalent and is not needed.

Takeaways

  • One open document = one session = one token. Tokens are per browser session, not global URLs.
  • The token expires on a sliding window; a viewer left idle past DocOptions.TimeOut needs a re-open.
  • Viewer can be injected and shared freely; sessions carry all the state.

Apakah halaman ini membantu?