Tutorial: Open Documents with the Injected Doconut Viewer in .NET 8
← Back to Blog4 min read

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.

Abstract server components passing an opaque session token to a document viewing surface
Abstract server components passing an opaque session token to a document viewing surface

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 patternCurrent .NET 8 direction
new Viewer(cache, accessor, licensePath)Inject Viewer after AddDoconut()
Static license-loading calls in request codeConfigure license input in AddDoconut()
Synchronous OpenDocument(...) examplesUse OpenDocumentAsync(...)
An external or invented viewer CDNEmit embedded resources with ReferenceCss and ReferenceScripts
A generic JavaScript init() APIInitialize $('#div_ctlDoc').docViewer(...)
Persisting the viewer tokenPersist 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.

#Doconut#.NET 8#Document Viewer#ASP.NET Core#JavaScript