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).
Inject it wherever you need it:
app.MapPost("/api/open", async (string fileName, Viewer viewer) =>
{
var token = await viewer.OpenDocumentAsync($"files/{fileName}");
return Results.Content(token, "text/plain");
});What happens inside OpenDocumentAsync
- License gate. A rejected or version-expired license (blacklisted, tampered, or a build outside the license's update window) throws a
LicenseExceptionimmediately, 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. - 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
IMemoryCacheunder a fresh GUID token with a sliding expiration —DocOptions.TimeOutminutes, default 60. Every page request resets the clock. - Security registration. With
UnsafeMode = false(the default), the token is bound to the caller's ASP.NET session: asecure-{token}marker is written into the session, so only the browser session that opened the document can request its pages. - The token is returned. It is the single credential for everything that follows.
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:
| Query | Purpose |
|---|---|
?token=…&page=N | Rendered page image (PNG) |
?token=…&page=N&thumb=1 | Thumbnail |
?token=…&zoom=… | Zoomed page rendering |
?token=…&search=term | Full-text search (license-gated) |
?token=…&bookmarks | Document outline/bookmarks |
?token=…© / &showlinks / &fileFormat / &meta | Text copy, hyperlinks, format info, DICOM technical metadata |
?token=…&action=rotate/flip/close | Page actions and explicit close |
?token=…&AnnSave=… / &AnnLoad | Save/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 withSession 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.
Closing a document
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.
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.TimeOutneeds a re-open. Viewercan be injected and shared freely; sessions carry all the state.
Was this page helpful?