Katalyst.Library.PDF
1.0.0
dotnet add package Katalyst.Library.PDF --version 1.0.0
NuGet\Install-Package Katalyst.Library.PDF -Version 1.0.0
<PackageReference Include="Katalyst.Library.PDF" Version="1.0.0" />
<PackageVersion Include="Katalyst.Library.PDF" Version="1.0.0" />
<PackageReference Include="Katalyst.Library.PDF" />
paket add Katalyst.Library.PDF --version 1.0.0
#r "nuget: Katalyst.Library.PDF, 1.0.0"
#:package Katalyst.Library.PDF@1.0.0
#addin nuget:?package=Katalyst.Library.PDF&version=1.0.0
#tool nuget:?package=Katalyst.Library.PDF&version=1.0.0
Katalyst.Library.PDF
Overview
Library.PDF renders Razor templates to HTML or PDF using RazorLight and Playwright (Chromium). Templates are loaded from the file system under the host content root; configure the subdirectory with RazorTemplateBasePath.
Capabilities
- Razor to HTML or PDF — Compile and render
.cshtmlkeys under the configured template root, then optionally print to PDF through the active provider. - Configuration-driven provider — Choose the PDF engine with
Documents:PDF:Provider; built-in support isPlaywright(PDFConstants.ProviderNames). Comparison is case-insensitive. - Shared infrastructure — RazorLight engine, Playwright browser host, and concurrency gate are registered as singletons;
IPDFServiceis scoped so each request (or explicit scope) gets its own coordinator while heavy resources stay shared.
PDFConstants is a static class for shared literals (for example PDFConstants.ProviderNames.Playwright), aligned with PDFSettings.Provider and the checks in PDFServiceCollectionExtensions.
- Concurrency cap —
Playwright:MaxConcurrentTaskslimits concurrent PDF pipelines (each uses a dedicated browser context) to control Chromium memory use (PlaywrightConcurrencyGate).
Configuration shape
{
"Documents": {
"PDF": {
"Provider": "Playwright",
"Playwright": {
"MaxConcurrentTasks": 4
},
"RazorTemplateBasePath": "Templates/PDF",
"OutputPath": "Outputs/PDF",
"Settings": {
"PrintBackground": true,
"Format": "A4",
"Margin": {
"Top": "1cm",
"Bottom": "1cm",
"Left": "1cm",
"Right": "1cm"
}
}
}
}
}
Everything sits under the Documents:PDF section (see PDFSettings, constant PDFSettings.SectionName). Provider must resolve to a registered IPDFGenerator: AddAppPDF (PDFServiceCollectionExtensions) wires Playwright only. If Provider is not Playwright, registration throws InvalidOperationException unless you change that extension or supply your own composition root.
Calling AddAppPDF binds PDFSettings with services.Configure<PDFSettings>(configuration.GetSection(PDFSettings.SectionName)), registers RazorLight against Path.Combine(hostEnvironment.ContentRootPath, RazorTemplateBasePath), registers Playwright infrastructure, and registers IPDFService as scoped (implementation PlaywrightPDFService when Provider is Playwright).
Path (under Documents:PDF) |
Description |
|---|---|
Provider |
Built-in: Playwright. Other values require a different IPDFGenerator registration strategy than the stock extension. |
Playwright:MaxConcurrentTasks |
Caps concurrent PDF pipelines (PlaywrightConcurrencyGate). Binds to PlaywrightSettings under Documents:PDF:Playwright. |
RazorTemplateBasePath |
Folder relative to content root containing .cshtml templates (default Templates). |
OutputPath |
Optional folder relative to host content root; used by GetOutputDirectory and Render*ToFileAsync on IPDFService. |
Settings |
Passed to Playwright Page.PdfAsync: PrintBackground, Format, Margin (DocumentSettings, MarginSettings). |
IPDFService methods
| Method | Description |
|---|---|
RenderHtmlAsync |
Compiles and renders a template under RazorTemplateBasePath to HTML. |
RenderTemplateToPdfAsync |
HTML from template, then PDF bytes (subject to the concurrency gate). |
RenderHtmlToPdfAsync |
PDF bytes from existing HTML. |
RenderTemplateToPdfToFileAsync |
Renders a template to PDF and writes to disk; returns the absolute file path. |
RenderHtmlToPdfToFileAsync |
Writes HTML-based PDF to disk; returns the absolute file path. |
GetOutputDirectory |
Resolves the configured output directory (with optional explicit content root overload). |
Scoped lifetime — In ASP.NET Core the host opens a scope per HTTP request, so endpoints and controllers receive a fresh IPDFService per request. Outside the web pipeline (console hosts, background work), create a scope with IServiceScopeFactory.CreateScope (or CreateAsyncScope), resolve IPDFService from that scope’s IServiceProvider, then dispose the scope when finished (see the PDF.Utility sample host).
templateKey is the key RazorLight uses inside the template root—for example Invoice for root-level Invoice.cshtml, or a relative path for nested templates.
Playwright provider
When it runs
Selected when Documents:PDF:Provider is Playwright. Registers PlaywrightPDFGenerator as IPDFGenerator, PlaywrightPDFService as IPDFService, plus PlaywrightBrowserHost, PlaywrightConcurrencyGate, and a file-system IRazorLightEngine.
How it works
RazorTemplateBasePathis combined withIHostEnvironment.ContentRootPathto build the RazorLight project root; templates are compiled with an in-memory cache.- PDF generation uses a shared Chromium instance (
PlaywrightBrowserHost); each operation uses a new browser context. Documents:PDF:Settingsmap to Playwright print options (PrintBackground,Format,Marginstrings such as1cm).MaxConcurrentTasksthrottles how many PDF pipelines run at once across the process.
Configuration example
{
"Documents": {
"PDF": {
"Provider": "Playwright",
"Playwright": {
"MaxConcurrentTasks": 4
},
"RazorTemplateBasePath": "Templates/PDF",
"OutputPath": "Outputs/PDF",
"Settings": {
"PrintBackground": true,
"Format": "A4",
"Margin": {
"Top": "1cm",
"Bottom": "1cm",
"Left": "1cm",
"Right": "1cm"
}
}
}
}
}
Chromium installation
Playwright’s .NET package does not ship the Chromium binary inside Library.PDF; the host executable (web app, console, or test project) that references Microsoft.Playwright gets playwright.ps1 (and related CLI assets) in its build output after dotnet build. Install browsers once per machine or container image from that output folder.
Typical commands (adjust net10.0 / Debug to match your build):
- PowerShell Core (Windows, macOS, Linux): from the host project directory, run
pwsh bin/Debug/net10.0/playwright.ps1 install chromium - Windows PowerShell:
powershell -NoProfile -ExecutionPolicy Bypass -File .\bin\Debug\net10.0\playwright.ps1 install chromium - Bash (Linux, macOS):
bash bin/Debug/net10.0/playwright.sh install chromium
If you run dotnet run from the repository root, use the path to the built host output (for example YourApp/bin/Debug/net10.0/playwright.ps1). In CI or Docker, run the same install step after dotnet publish so the image contains Chromium under the Playwright browser cache. See Install browsers in the Playwright for .NET documentation.
Browser cache location (optional)
Playwright stores downloaded browsers in a cache directory. You can control it with the PLAYWRIGHT_BROWSERS_PATH environment variable (shared cache on build agents, read-only pre-seeded paths in containers, etc.). PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 is useful when the image already contains browsers. Details are in Playwright’s browsers guide.
Launch behavior in this library
PlaywrightBrowserHost always launches Playwright’s Chromium with Headless = true and does not read Documents:PDF for executable path, channel, or extra BrowserTypeLaunchOptions. To use a system Chrome build, custom flags, or a non-default install location, replace or extend PlaywrightBrowserHost (or register your own IPDFGenerator) in your composition root.
Requirements
- .NET 10 SDK (matches this library’s
net10.0target). - Chromium installed for the host application’s Playwright runtime, as in Chromium installation above.
Examples
ASP.NET Core (minimal hosting)
using Library.PDF;
using Library.PDF.Contracts;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAppPDF(builder.Configuration, builder.Environment);
var app = builder.Build();
app.MapGet("/reports/sample.pdf", async (IPDFService pdf, CancellationToken ct) =>
{
var model = new Dictionary<string, object>
{
["Title"] = "Monthly report",
["Items"] = new List<object> { "A", "B" },
};
var bytes = await pdf.RenderTemplateToPdfAsync("Sample", model, ct);
return Results.File(bytes, "application/pdf", "sample.pdf");
});
app.Run();
appsettings.json
{
"Documents": {
"PDF": {
"Provider": "Playwright",
"RazorTemplateBasePath": "Templates/PDF",
"OutputPath": "Outputs/PDF",
"Playwright": {
"MaxConcurrentTasks": 4
},
"Settings": {
"PrintBackground": true,
"Format": "A4",
"Margin": {
"Top": "1cm",
"Bottom": "1cm",
"Left": "1cm",
"Right": "1cm"
}
}
}
}
}
Put .cshtml files under {ContentRoot}/{RazorTemplateBasePath} (for example Templates/Invoice.cshtml). The template key is usually the file name without .cshtml, for example "Invoice" for Invoice.cshtml.
Controller
using Library.PDF.Contracts;
using Microsoft.AspNetCore.Mvc;
public class DocumentsController(IPDFService pdf) : ControllerBase
{
[HttpGet("invoice.pdf")]
public async Task<IActionResult> Invoice(CancellationToken cancellationToken)
{
var model = new Dictionary<string, object>
{
["DocumentTitle"] = "Invoice INV-001",
["GrandTotal"] = "$199.50",
["DueAmount"] = "$199.50",
};
var bytes = await pdf.RenderTemplateToPdfAsync("Invoice", model, cancellationToken);
return File(bytes, "application/pdf", "invoice.pdf");
}
}
For a full nested dictionary shape that drives every section of the bundled Invoice.cshtml, see the PDF.Utility sample host (BuildMinimalInvoiceModel).
Strongly typed @model
public sealed record InvoiceViewModel(string DocumentTitle, decimal Total);
var model = new InvoiceViewModel("INV-001", 199.50m);
var bytes = await pdf.RenderTemplateToPdfAsync("Invoice", model, cancellationToken);
HTML only: call RenderHtmlAsync. To write PDFs under the configured output folder, use RenderTemplateToPdfToFileAsync and GetOutputDirectory().
Class library, console, or tests (Host + explicit scope)
Use this when you are not using WebApplicationBuilder but want the same registration and a scoped IPDFService (mirrors PDF.Utility):
using Library.PDF;
using Library.PDF.Contracts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddAppPDF(builder.Configuration, builder.Environment);
using var host = builder.Build();
using var scope = host.Services.CreateScope();
var pdf = scope.ServiceProvider.GetRequiredService<IPDFService>();
var bytes = await pdf.RenderTemplateToPdfAsync("Sample", model, CancellationToken.None);
For manual ConfigurationBuilder without a host, add IConfiguration, IHostEnvironment (or a test stub with ContentRootPath pointing at your template folder), logging, then AddAppPDF—the content root you pass must contain RazorTemplateBasePath.
Custom implementation
If you replace PDF generation entirely, register your own IPDFService (and any supporting types) instead of relying on AddAppPDF, or extend the library by registering a custom IPDFGenerator and adjusting AddAppPDF to select it for your Provider value.
Referenced packages
The project references RazorLight, Microsoft.Playwright, Microsoft.Extensions.DependencyInjection.Abstractions, Microsoft.Extensions.Hosting.Abstractions, Microsoft.Extensions.Logging.Abstractions, Microsoft.Extensions.Options.ConfigurationExtensions, and Microsoft.Extensions.Caching.Memory. The explicit Microsoft.Extensions.Caching.Memory reference ensures restore resolves a current version (RazorLight still pulls an older transitive copy affected by GHSA-qj66-m88j-hmgj). Hosting apps typically already reference Microsoft.AspNetCore.App or Microsoft.Extensions.Hosting, which supply IServiceCollection extensions and IConfiguration.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Caching.Memory (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Microsoft.Playwright (>= 1.59.0)
- RazorLight (>= 2.3.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 123 | 5/14/2026 |