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:
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
- 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.
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:
| 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 | Text copy, hyperlinks, and format info |
?token=…&meta | DICOM technical metadata; returns 501 for a DICOM session on this package |
?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.
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
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.TimeOutneeds a re-open. Viewercan be injected and shared freely; sessions carry all the state.
หน้านี้เป็นประโยชน์หรือไม่?