
Tutorial: Open Documents with the Injected Doconut Viewer in .NET 8
Introduction
Older Doconut examples may construct Viewer directly with cache, HTTP-context, and license-path arguments. That is not the current .NET 8 integration model. AddDoconut() registers Viewer with dependency injection, and application endpoints receive the service rather than calling a constructor.

This tutorial follows the current request flow: register services and middleware, emit the embedded viewer resources, open a document with OpenDocumentAsync, return an opaque session token, and pass that token to the browser widget.
1. Install and register Doconut
Add the .NET 8 package:
dotnet add package Doconut.NET8
Register Doconut and ASP.NET session services:
builder.Services.AddDoconut(options =>
{
options.LicensePath = "Doconut.Viewer.lic";
options.MiddlewarePath = "/doconut";
options.ResourcesPath = "/doconut-res";
options.UnsafeMode = false;
});
builder.Services.AddSession();
Wire the middleware in the required order. Resource middleware must run before the terminal document middleware:
app.UseRouting();
app.UseSession();
app.UseDoconutResources();
app.Map("/doconut", branch => branch.UseDoconut());
MiddlewarePath coordinates configuration but does not create the ASP.NET branch by itself. The mapped /doconut path must match the widget's BasePath.
2. Add the viewer surface and resources
The Doconut browser viewer is a jQuery plugin. In a Razor page, inject Viewer and ask it to emit the resource tags in dependency order:
@inject Doconut.Viewer Viewer
@Html.Raw(Viewer.ReferenceCss(new CssConfig
{
IncludeViewerCss = true
}))
@Html.Raw(Viewer.ReferenceScripts(new ScriptConfig
{
IncludeJQuery = true,
IncludeViewerScripts = true
}))
<div id="divDocViewer">
<div id="div_ctlDoc"></div>
</div>
Initialize the widget with paths that match the server registration:
const objViewer = $('#div_ctlDoc').docViewer({
showThumbs: true,
autoLoad: false,
pageZoom: 100,
FitType: 'width',
BasePath: '/doconut',
ResPath: '/doconut-res/images',
onError: function (message) {
console.error('Doconut viewer error:', message);
}
});
The option casing is significant. Use the names shown by the installed version instead of normalizing them to a single style.
3. Inject Viewer and open a document
Viewer is registered as a transient service. Resolve it through endpoint injection, constructor injection, or the equivalent facility in your ASP.NET Core application.
app.MapPost("/api/open", async (
Viewer viewer,
CancellationToken ct) =>
{
string token = await viewer.OpenDocumentAsync(
"wwwroot/files/Sample.pdf",
ct: ct);
return Results.Ok(new { token });
});
For an upload, provide a stream and a FileInfo whose extension identifies the source format:
app.MapPost("/api/open-upload", async (
IFormFile file,
Viewer viewer,
CancellationToken ct) =>
{
await using var stream = file.OpenReadStream();
string token = await viewer.OpenDocumentAsync(
stream,
new FileInfo(file.FileName),
ct: ct);
return Results.Ok(new { token });
});
Validate upload size, extension, and authorization before opening user-supplied content. Do not turn the submitted filename into a server path.
4. Pass the token to the widget
Fetch the open endpoint and hand the returned token to objViewer.View:
fetch('/api/open', { method: 'POST' })
.then(response => {
if (!response.ok) throw new Error('The document could not be opened.');
return response.json();
})
.then(data => objViewer.View(data.token))
.catch(error => console.error(error));
Treat the token as a bearer credential for a live document session:
- Do not log or persist it.
- Return it only to an authorized client.
- Do not expose the source file path.
- Reopen the document when a session expires.
- Close the session when the document is no longer needed.
5. Close server-side sessions deliberately
Client code can call objViewer.Close() when the user leaves the viewer. Server workflows can also revoke a known token explicitly:
app.MapPost("/api/close", (string token, Viewer viewer) =>
{
viewer.CloseDocument(token);
return Results.NoContent();
});
Explicit close is especially useful for large documents. Session expiration remains a fallback, not a substitute for predictable application lifecycle management.
6. Add optional modules only after the core works
Search and annotations attach to the same initialized viewer. Add their CSS, scripts, mounts, licensing checks, and lifecycle callbacks only after the base flow succeeds:
AddDoconut + session services
-> UseSession
-> UseDoconutResources
-> mapped UseDoconut branch
-> viewer resources and mount
-> initialize docViewer
-> OpenDocumentAsync
-> objViewer.View(token)
This order keeps core rendering failures separate from optional module configuration.
Common migration mistakes
| Old or incorrect pattern | Current .NET 8 direction |
|---|---|
new Viewer(cache, accessor, licensePath) | Inject Viewer after AddDoconut() |
| Static license-loading calls in request code | Configure license input in AddDoconut() |
Synchronous OpenDocument(...) examples | Use OpenDocumentAsync(...) |
| An external or invented viewer CDN | Emit embedded resources with ReferenceCss and ReferenceScripts |
A generic JavaScript init() API | Initialize $('#div_ctlDoc').docViewer(...) |
| Persisting the viewer token | Persist your document ID; treat the token as temporary |
Use the official Doconut documentation and verify examples against the installed package version before adapting them to production code.