Viewer is Stateless — How to Build a Minimal .NET 8 Open Endpoint
← Back to Blog••4 min read

Viewer is Stateless — How to Build a Minimal .NET 8 Open Endpoint

Viewer is Stateless — by Design

Viewer does not keep any per‑request document data. It is a sealed class, it never implements IDisposable, and every document you open lives in a separate session that the framework’s cache manages. Because of that you can inject a single Viewer instance anywhere in your ASP.NET Core app and safely call it from many concurrent requests.


Consequences of a stateless service

  • No hidden resources – the service itself never allocates large buffers that need explicit cleanup.
  • Thread‑safe by default – because no request‑specific state is stored, the same Viewer instance can be used by multiple threads without locking.
  • Scalable deployments – horizontal scaling works out‑of‑the‑box; you don’t need sticky sessions or server affinity, only the session token that the client holds.

All document‑specific information (pages, annotations, search index) is kept in a session created by OpenDocumentAsync. The session is stored in IMemoryCache (or any IDistributedCache you configure) and expires automatically after a period of inactivity (default 60 minutes). When you are done you can call CloseDocument to free the cache entry early.


Registering the stateless Viewer

Because Viewer has no mutable state, register it once during startup:

builder.Services.AddDoconut();               // registers Viewer and required middleware
builder.Services.AddSession();               // required for token security
builder.Services.AddDistributedMemoryCache(); // simple cache for the demo

The AddDoconut call also wires the middleware that serves the viewer’s static resources. After the services are in place you can request Viewer in any endpoint signature.


Minimal open‑document endpoint (net‑8 example)

The following code is the exact snippet from the official documentation and works out of the box in a .NET 8 minimal‑API project:

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

What the code does

  1. Accepts a file name – the client tells the server which file under wwwroot/files to open.
  2. Calls OpenDocumentAsync – the stateless Viewer creates a new session, stores it in the cache, and returns an opaque token.
  3. Returns the token as plain text – the client receives only the token; the server never sends the document bytes in this response.

Because Viewer holds no document data, the endpoint does not need using blocks, Dispose calls, or per‑request factories. The token is the only credential the client will ever send back to the viewer widget (objViewer.View(token) on the front‑end).


Using the token on the client side

After the token is returned, the front‑end widget calls objViewer.View(token). The widget then includes the same token with every subsequent request for page images, search, or annotation actions. The server validates the token against the session that originally opened the document. If the token is presented from a different browser session, the viewer returns an authorization error, which is the intended security behavior.


Closing a session early (optional)

If your workflow knows that a document will never be accessed again, you can free the cache entry by invoking CloseDocument on the Viewer. This removes the session from the cache and prevents the sliding‑expiration timer from keeping it alive.


Recap of the stateless flow

StepWhat happensWhere state lives
Inject ViewerA single Viewer instance is created at startup.In the DI container (singleton).
OpenDocumentAsyncLicense checked → session created → token returned.Session stored in IMemoryCache (or distributed cache).
Client rendersWidget sends token with every request.Token is the only per‑document data the client holds.
Session expiration / CloseDocumentCache entry removed after timeout or explicit close.No lingering resources in the Viewer service.

Because the service itself never stores document data, you can safely scale out your web farm, replace nodes, or restart the application without losing any user‑visible state—only the token becomes invalid, prompting the client to reopen the document.


Deploying the stateless Viewer in your app

  1. Add AddDoconut, AddSession, and a cache provider to Program.cs.
  2. Paste the open‑endpoint snippet into your minimal‑API configuration.
  3. Pass the returned token to objViewer.View(token) on the front‑end.

You now have a fully stateless, production‑ready document‑open API that leverages Doconut’s Viewer design.

#dotnet8#viewer#stateless#api#doconut