ASP.NET Core

Three middleware calls, not a rewrite

Doconut is registered the way everything else in ASP.NET Core is registered: a service in the container and middleware in the pipeline. It inherits your authentication, your logging, your DI graph and your deployment story, because it is running inside them rather than beside them.

3
middleware calls to integrate
75
file extensions out of the box
2
deployment targets: Windows, Docker

The problem

The integration tax nobody budgets for

Most document viewers arrive as a separate service. That means a second deployment unit, a second set of credentials, a network hop your documents now travel across, and a second thing to page someone about at 2am.

Doconut is a library. AddDoconut() puts it in your service collection; UseDoconut() puts it in your pipeline. It runs under your process identity, sees your configuration, writes to your logger, and is deployed by whatever already deploys your application.

The practical consequence is that authorization stays where it belongs. You call OpenDocumentAsync() after your own permission check, and the viewer can only ever render what you decided to hand it.

Capabilities

What the middleware gives you

Razor Pages, MVC and minimal APIs

The viewer is not tied to a hosting style. Render the mount div from a Razor view or a static page and open the document from a controller action, a page handler or a mapped endpoint.

Your authentication, unchanged

Because the endpoints live in your pipeline, [Authorize] works the way it always did. There is no second identity system to federate with.

Session-backed document security

Document security rides on ASP.NET session state, which is why UseSession() has to be registered before UseDoconut(). It means the viewer's notion of who you are is the same as the application's.

Web farm ready

Multiple nodes behind a load balancer share the render cache, so a session opened on one node keeps working when the next request lands elsewhere.

Windows or Docker

IIS, Kestrel, or a container image you build yourself. Nothing about the integration changes between them except where the licence file is mounted.

Conversion in the same pipeline

With the Converter plugin, DocumentConverter.ConvertAsync() runs in the same process — no second service, no temporary upload, no round trip.

Integration

Registration and an open endpoint

UserMayRead and ResolvePath are your own code. That is the point: Doconut never learns which documents exist or who is allowed to see them.

Supported platforms

Razor PagesMVCMinimal APIs.NET 8.NET 6WindowsDocker
csharp
// Program.cs
builder.Services.AddDoconut(options =>
{
    options.LicensePath = "Doconut.Viewer.lic";
});
builder.Services.AddSession(); // document security rides on session state

var app = builder.Build();

app.UseSession();          // call UseSession() before UseDoconut()
app.UseDoconutResources(); // must be registered before UseDoconut()
app.UseDoconut();

// Open the document server-side, behind your own authorization
app.MapPost("/api/open", async (Viewer viewer, HttpContext ctx, string documentId) =>
{
    if (!await ctx.UserMayRead(documentId))
        return Results.Forbid();

    // The token is opaque — hand it to the widget, never log or persist it.
    string token = await viewer.OpenDocumentAsync(ResolvePath(documentId));
    return Results.Ok(new { token });
}).RequireAuthorization();

Details

Registration order and gotchas

  • UseSession() must come before UseDoconut(). Document security depends on it.
  • UseDoconutResources() must come before UseDoconut(), and should sit behind the same authentication as the rest of the app.
  • The Razor view injects Doconut.Viewer and emits ReferenceCss / ReferenceScripts; jQuery has to load before the viewer scripts.
  • Set options.LicensePath from configuration so the licence file can be mounted as a secret rather than baked into the image.

Frequently asked questions

Does it work with .NET 6 as well as .NET 8?

Yes. Both are supported and use the same DI-plus-middleware architecture. There are dedicated pages for each if you need version-specific detail.

Is there a Razor component or a tag helper?

No, and that is deliberate. Integration is always middleware plus the JavaScript widget, which keeps the same integration valid across Razor Pages, MVC, Web Forms and Blazor instead of fragmenting into four.

How does it behave behind a load balancer?

Web farm and distributed deployment are supported through a shared render cache. A document opened on one node stays readable when subsequent requests hit another.

Do I need Office installed on the server?

No. Rendering is native — there is no Office interop, no headless Word, and no COM automation to babysit.

Try it against your own documents

A temporary licence takes a few minutes to request and runs entirely on your own machine. The files that matter are the ones already breaking your current viewer.