License Setup

Where Doconut looks for your license file

Without a license, Doconut still renders documents — every page just carries an evaluation watermark. This page covers the four ways to supply a license, and the exact precedence when more than one is set.

Four ways to supply a license

There are four: three explicit sources on DoconutOptions — a stream, raw content, or a file path — plus automatic discovery when none of them is set. When more than one is set, the precedence is exact:

LicenseStream beats LicenseContent beats LicensePath beats auto-search.

By path

LicensePath is passed to File.Exists exactly as given. A relative path resolves against the process's current working directory — not your project folder, and not the folder Program.cs lives in. If the path does not resolve, Doconut does not throw and does not fall back to auto-search — it simply loads no license and the viewer watermarks. Auto-search only runs when none of LicensePath, LicenseContent, or LicenseStream is set. The per-plugin license files in the same folder (Doconut.Viewer.<Capability>.lic) are read with it, so LicensePath and AddPlugin<...>() work together when the plugin licenses sit beside the base license.

Prefer an absolute path (for example built from IWebHostEnvironment.WebRootPath or AppContext.BaseDirectory), or skip LicensePath entirely and rely on automatic discovery below.

csharp
builder.Services.AddDoconut(options =>
{
    options.LicensePath = Path.Combine(AppContext.BaseDirectory, "wwwroot", "Doconut.Viewer.lic");
});

By stream

LicenseStream is read once at startup — useful when the license comes from a secret store rather than a file on disk.

csharp
// Precedence: LicenseStream > LicenseContent > LicensePath > auto-search.
builder.Services.AddDoconut(options =>
{
    options.LicenseStream = licenseStream;
});

By content

LicenseContent accepts the license text itself — from an environment variable, a database, or a secret manager:

csharp
// License XML from a database, environment variable, or secret manager —
// no file on disk. Beaten only by LicenseStream.
builder.Services.AddDoconut(options =>
{
    options.LicenseContent = Environment.GetEnvironmentVariable("DOCONUT_LICENSE") ?? "";
});

Automatic discovery

Configure none of the three explicit sources, and Doconut searches for the license itself:

csharp
// Configure nothing, and Doconut searches for the license itself, in these two folders only:
//   1. {CurrentDirectory}/wwwroot
//   2. {AppContext.BaseDirectory}/wwwroot   (wwwroot inside the build output folder)
// It looks for Doconut.lic first, then Doconut.Viewer.lic, plus any per-plugin
// Doconut.Viewer.<Capability>.lic files alongside it.
builder.Services.AddDoconut();

The probe folders, in order, and the filenames looked for in each:

text
1. {CurrentDirectory}/wwwroot
2. {AppContext.BaseDirectory}/wwwroot

Filenames (checked in each folder above, in order):
  Doconut.lic                          — base viewer license (checked first)
  Doconut.Viewer.lic                   — base viewer license
  Doconut.Viewer.<Capability>.lic      — per-plugin license, alongside the base license

Nothing outside a wwwroot folder is read: a .lic in the output folder itself, or in wwwroot/lib, is ignored.

Copy the license to your output folder

When the process does not run from the project folder — a published app, IIS, a container, a test runner — the first probe misses, and the second one, {AppContext.BaseDirectory}/wwwroot, has to find the file. The SDK's own test app copies the licenses there on every build with this MSBuild target:

xml
<Target Name="CopyLicensesToOutput" AfterTargets="Build">
  <ItemGroup>
    <DoconutLicenseFiles Include="$(MSBuildProjectDirectory)\wwwroot\*.lic" />
    <StaleOutputLicenses Include="$(OutDir)wwwroot\*.lic" />
  </ItemGroup>
  <!-- Delete first, so renaming or removing a .lic in wwwroot also takes it out of the output. -->
  <Delete Files="@(StaleOutputLicenses)" />
  <Copy SourceFiles="@(DoconutLicenseFiles)" DestinationFolder="$(OutDir)wwwroot" />
</Target>

The destination must be $(OutDir)wwwroot: a .lic copied to the output root is never found by auto-search. The LicensePath examples on this page point at the same place.

Keep .lic files out of source control — deploy them alongside the app, or inject the license through LicenseContent or LicenseStream from your secret store.

What happens without a license

A missing license does not throw. AddDoconut() succeeds, the app starts, and the viewer runs — but every page carries an evaluation watermark and no optional capability is granted.

A license file that is found but rejected is different. An invalid signature, tampering, blacklisting, or a build outside the license's version window causes OpenDocumentAsync to throw LicenseException with License.RejectionMessage. A calendar-expired license that has no rejection message continues in watermarked mode.

Plugins need capabilities

Registering a plugin with no corresponding entitlement is different: for a missing license, a legacy TRIAL file, or a paid license without that capability, AddDoconut() throws InvalidOperationException, so the app does not start. For example, registering the Converter plugin without a license that grants Converter:

text
InvalidOperationException: The Converter plugin is registered via AddPlugin but no active
license grants Converter. Remove the AddPlugin<...>() call or install a license (or
trial/demo) that includes Converter.

The message tells you the fix directly: either remove the options.AddPlugin<...>() call for that plugin, or install a paid license or an active Temporary/Demo (NFR) license that grants the capability. Temporary registrations are allowed to survive their expiry date so an already configured app can degrade at runtime instead of crashing during restart; once expired, their capabilities are still revoked.

Verify the loaded license

Use IDoconutLicenseService, the same source of truth used by the SDK, to expose an authenticated diagnostic endpoint or to drive feature flags. Do not return license contents or keys.

csharp
app.MapGet("/api/doconut/license", (IDoconutLicenseService license) => Results.Ok(new
{
    viewer = license.IsViewerLicensed || license.IsTemporary,
    temporary = license.IsTemporary,
    search = license.IsCapabilityGranted(LicenseCapability.Search),
    annotation = license.IsCapabilityGranted(LicenseCapability.Annotation),
    converter = license.HasConverter,
    dicom = license.HasDicom
}));

The license is read during AddDoconut() registration. ResetLicense is currently a compatibility property with no active reload path, so replacing a license file requires restarting the application.

Troubleshooting matrix

SymptomLikely causeCheck
Viewer works but every page is watermarkedNo license was loaded, or the license is calendar-expiredResolve IDoconutLicenseService; verify the output directory and process working directory
AddDoconut() throws for a pluginThe license does not grant that plugin capabilityCheck IsCapabilityGranted(...) and remove registrations you did not purchase
A configured relative path works locally but not in IIS/containerThe process working directory changedUse AppContext.BaseDirectory or an absolute path
Replaced .lic file has no effectThe singleton license service was already createdRestart the application
OpenDocumentAsync throws LicenseExceptionSignature, domain, version window, blacklist, or plugin runtime gate rejected the licenseRead the exception/rejection message without exposing it to untrusted clients

Next steps

  • Licensing — capabilities, license tiers, and verifying what was loaded at runtime.
  • Troubleshooting — watermarks, rejected licenses, and capability errors.

Was this page helpful?