AdaskoTheBeAsT.WkHtmlToX.Hosting
11.1.0
dotnet add package AdaskoTheBeAsT.WkHtmlToX.Hosting --version 11.1.0
NuGet\Install-Package AdaskoTheBeAsT.WkHtmlToX.Hosting -Version 11.1.0
<PackageReference Include="AdaskoTheBeAsT.WkHtmlToX.Hosting" Version="11.1.0" />
<PackageVersion Include="AdaskoTheBeAsT.WkHtmlToX.Hosting" Version="11.1.0" />
<PackageReference Include="AdaskoTheBeAsT.WkHtmlToX.Hosting" />
paket add AdaskoTheBeAsT.WkHtmlToX.Hosting --version 11.1.0
#r "nuget: AdaskoTheBeAsT.WkHtmlToX.Hosting, 11.1.0"
#:package AdaskoTheBeAsT.WkHtmlToX.Hosting@11.1.0
#addin nuget:?package=AdaskoTheBeAsT.WkHtmlToX.Hosting&version=11.1.0
#tool nuget:?package=AdaskoTheBeAsT.WkHtmlToX.Hosting&version=11.1.0
WkHtmlToX
๐ A high-performance, thread-safe C# wrapper for wkhtmltopdf that converts HTML to PDF and images with ease.
โจ Features
- Cross-Platform Support - Works on Windows (x64/x86), macOS, and Linux (multiple distributions)
- Thread-Safe - Built with concurrency in mind using modern .NET patterns
- Memory Efficient - Leverages
ArrayPool<byte>and supports RecyclableMemoryStream - Async/Await - Fully asynchronous API with
CancellationTokensupport - Multi-Target - Supports .NET Framework 4.6.2+, .NET 8.0, .NET 9.0, and .NET 10.0
- Production Ready - Comprehensive test coverage and extensive static analysis
- Event Callbacks - Track conversion progress, phases, warnings, and errors
- HTML to PDF - Convert HTML content, streams, or byte arrays to PDF
- HTML to Image - Convert HTML to various image formats
๐ฆ Installation
# Core package
dotnet add package AdaskoTheBeAsT.WkHtmlToX
# Optional: plain Microsoft.Extensions.DependencyInjection integration
dotnet add package AdaskoTheBeAsT.WkHtmlToX.DependencyInjection
# Optional: generic-host (IHostedService) driven lifecycle
dotnet add package AdaskoTheBeAsT.WkHtmlToX.Hosting
๐ Quick Start
Console Application
using AdaskoTheBeAsT.WkHtmlToX;
using AdaskoTheBeAsT.WkHtmlToX.Documents;
using AdaskoTheBeAsT.WkHtmlToX.Engine;
using AdaskoTheBeAsT.WkHtmlToX.Settings;
var configuration = new WkHtmlToXConfiguration((int)Environment.OSVersion.Platform, null);
using var engine = new WkHtmlToXEngine(configuration);
engine.Initialize();
var converter = new PdfConverter(engine);
// Create document with HTML content
var document = new HtmlToPdfDocument
{
GlobalSettings = new PdfGlobalSettings
{
ColorMode = ColorMode.Color,
Orientation = Orientation.Portrait,
PaperSize = PaperKind.A4
},
ObjectSettings =
{
new PdfObjectSettings
{
HtmlContent = "<html><body><h1>Hello World!</h1></body></html>",
WebSettings = { DefaultEncoding = "utf-8" }
}
}
};
// Convert to PDF
using var stream = new FileStream("output.pdf", FileMode.Create);
var result = await converter.ConvertAsync(
document,
_ => stream,
CancellationToken.None);
Console.WriteLine(result ? "Success!" : "Failed!");
ASP.NET Core Web API with Dependency Injection
Starting with v11.0.0, DI and Hosting integration is shipped as two small
companion packages built on top of
AdaskoTheBeAsT.Interop.Execution.
See the Migration guide below if you are
upgrading from v10.x.
Option A - AdaskoTheBeAsT.WkHtmlToX.Hosting (recommended)
The hosted service drives worker InitializeAsync / DisposeAsync
through the generic host, so your app never calls engine.Initialize()
manually.
// Program.cs
using AdaskoTheBeAsT.WkHtmlToX.Engine;
using AdaskoTheBeAsT.WkHtmlToX.Hosting;
var builder = WebApplication.CreateBuilder(args);
var wkhtmlConfiguration = new WkHtmlToXConfiguration(
(int)Environment.OSVersion.Platform,
runtimeIdentifier: null);
builder.Services.AddWkHtmlToXHostedService(wkhtmlConfiguration, options =>
{
options.MaxOperationsPerSession = 500;
options.RecycleSessionOnFailure = true;
});
var app = builder.Build();
app.Run();
Option B - AdaskoTheBeAsT.WkHtmlToX.DependencyInjection
Use this when you are not running on the generic host (e.g. OWIN /
ASP.NET 4.8 on full framework). Call InitializeAsync on the resolved
engine during application startup and dispose it on shutdown.
using AdaskoTheBeAsT.WkHtmlToX.DependencyInjection;
using AdaskoTheBeAsT.WkHtmlToX.Engine;
services.AddWkHtmlToX(new WkHtmlToXConfiguration(
(int)Environment.OSVersion.Platform,
runtimeIdentifier: null));
Both packages register IWkHtmlToXEngine, IPdfConverter and
IImageConverter for injection.
Controller Implementation
[ApiController]
[Route("api/[controller]")]
public class PdfController : ControllerBase
{
private readonly IPdfConverter _pdfConverter;
private readonly RecyclableMemoryStreamManager _memoryManager;
public PdfController(IPdfConverter pdfConverter)
{
_pdfConverter = pdfConverter;
_memoryManager = new RecyclableMemoryStreamManager();
}
[HttpPost("convert")]
public async Task<IActionResult> ConvertToPdf([FromBody] string htmlContent)
{
var document = new HtmlToPdfDocument
{
GlobalSettings = new PdfGlobalSettings(),
ObjectSettings =
{
new PdfObjectSettings { HtmlContent = htmlContent }
}
};
Stream? stream = null;
var converted = await _pdfConverter.ConvertAsync(
document,
length =>
{
stream = _memoryManager.GetStream(
Guid.NewGuid(),
"wkhtmltox",
length);
return stream;
},
HttpContext.RequestAborted);
if (converted && stream != null)
{
stream.Position = 0;
return File(stream, "application/pdf", "output.pdf");
}
return BadRequest("Conversion failed");
}
}
๐ง Advanced Configuration
Linux Platform Configuration
For Linux environments, specify the runtime identifier:
var configuration = new WkHtmlToXConfiguration(
(int)PlatformID.Unix,
WkHtmlToXRuntimeIdentifier.Ubuntu2004X64);
Supported Linux Distributions
- Ubuntu (14.04, 16.04, 18.04, 20.04) - x64/x86
- Debian (9, 10) - x64/x86
- CentOS (6, 7, 8)
- Amazon Linux 2
- OpenSUSE Leap 15
Event Callbacks
Monitor conversion progress and handle warnings/errors:
var configuration = new WkHtmlToXConfiguration(
(int)Environment.OSVersion.Platform,
null)
{
PhaseChangedAction = args =>
Console.WriteLine($"Phase {args.CurrentPhase}/{args.PhaseCount}: {args.Description}"),
ProgressChangedAction = args =>
Console.WriteLine($"Progress: {args.Description}"),
WarningAction = args =>
Console.WriteLine($"Warning: {args.Message}"),
ErrorAction = args =>
Console.WriteLine($"Error: {args.Message}"),
FinishedAction = args =>
Console.WriteLine($"Finished: {(args.Success ? "Success" : "Failed")}")
};
Custom PDF Settings
var document = new HtmlToPdfDocument
{
GlobalSettings = new PdfGlobalSettings
{
ColorMode = ColorMode.Color,
Orientation = Orientation.Landscape,
PaperSize = PaperKind.A4,
Margins = new MarginSettings
{
Top = 10,
Bottom = 10,
Left = 10,
Right = 10,
Unit = Unit.Millimeters
},
DocumentTitle = "My Document",
UseCompression = true
},
ObjectSettings =
{
new PdfObjectSettings
{
HtmlContent = htmlContent,
WebSettings =
{
DefaultEncoding = "utf-8",
EnableJavascript = true,
LoadImages = true
},
HeaderSettings = new SectionSettings
{
HtmlContent = "<div>Header Content</div>"
},
FooterSettings = new SectionSettings
{
HtmlContent = "<div>Page [page] of [toPage]</div>"
}
}
}
};
Memory-Efficient Stream Handling
The library supports custom stream creation, allowing integration with RecyclableMemoryStream to minimize memory allocations:
var memoryManager = new RecyclableMemoryStreamManager();
Stream? stream = null;
var result = await converter.ConvertAsync(
document,
length =>
{
// RecyclableMemoryStream reuses memory blocks
stream = memoryManager.GetStream(Guid.NewGuid(), "wkhtmltox", length);
return stream;
},
cancellationToken);
๐ Migration guide (v10.x โ v11.0.0)
v11.0.0 is a breaking release that moves infrastructure concerns onto
the shared AdaskoTheBeAsT.Interop.* toolbox and splits DI / Hosting
into dedicated packages. There is no compatibility shim - follow
this guide when upgrading.
TL;DR
| Area | v10.x (before) | v11.0.0 (after) |
|---|---|---|
| Target frameworks | netstandard2.0, net8.0, net9.0, net10.0 |
net462, net472, net48, net481, net8.0, net9.0, net10.0 |
| DI registration | hand-written AddSingleton<IWkHtmlToXEngine>(...) in the core package |
AddWkHtmlToX(...) / AddWkHtmlToXHostedService(...) in companion packages |
| Engine worker | custom BlockingCollection + STA Thread inside WkHtmlToXEngine |
ExecutionWorker<WkHtmlToXSession> from AdaskoTheBeAsT.Interop.Execution |
| Native loader | Loaders/ + Native/*NativeMethods.cs |
UnmanagedLibrary from AdaskoTheBeAsT.Interop.Unmanaged |
ProgressChangedEventArgs ctor |
(document, description) |
(document, progress, description) + new Progress property |
| CI | Azure Pipelines (azure-pipelines.yml) |
GitHub Actions (.github/workflows/ci.yml) |
| Solution format | AdaskoTheBeAsT.WkHtmlToX.sln |
AdaskoTheBeAsT.WkHtmlToX.slnx |
1. netstandard2.0 is dropped
The core package no longer targets netstandard2.0. Supported TFMs are
net462, net472, net48, net481, net8.0, net9.0, net10.0.
If you still need netstandard2.0, stay on the v10.x line.
2. DI / Hosting moved to separate packages
In v10.x the recommended pattern was to new up the engine and call
Initialize() yourself:
// v10.x
services.AddSingleton(sp =>
new WkHtmlToXConfiguration((int)Environment.OSVersion.Platform, null));
services.AddSingleton<IWkHtmlToXEngine>(sp =>
{
var engine = new WkHtmlToXEngine(
sp.GetRequiredService<WkHtmlToXConfiguration>());
engine.Initialize();
return engine;
});
services.AddSingleton<IPdfConverter, PdfConverter>();
services.AddSingleton<IImageConverter, ImageConverter>();
In v11.0.0 pick one of the two companion packages:
// v11.0.0 - generic host (recommended for ASP.NET Core / worker services)
services.AddWkHtmlToXHostedService(
new WkHtmlToXConfiguration((int)Environment.OSVersion.Platform, null));
// v11.0.0 - plain DI (OWIN, non-hosted apps)
services.AddWkHtmlToX(
new WkHtmlToXConfiguration((int)Environment.OSVersion.Platform, null));
Both extensions register IWkHtmlToXEngine, IPdfConverter and
IImageConverter. Remove any leftover AddSingleton<IPdfConverter, ...>
/ AddSingleton<IImageConverter, ...> lines - they are done for you.
3. WkHtmlToXEngine constructor changed
WkHtmlToXEngine no longer owns the worker thread itself. It takes an
IExecutionWorker<WkHtmlToXSession> and optionally a flag that says
whether it owns the worker lifetime.
// v10.x
using var engine = new WkHtmlToXEngine(configuration);
engine.Initialize();
// v11.0.0 - via DI (preferred)
// resolve IWkHtmlToXEngine from the container; Hosting package drives
// InitializeAsync / DisposeAsync for you.
// v11.0.0 - manual wiring (console apps / tests)
var loaderFactory = new LibraryLoaderFactory();
var sessionFactory = new WkHtmlToXSessionFactory(configuration, loaderFactory);
await using var worker = new ExecutionWorker<WkHtmlToXSession>(
sessionFactory,
new ExecutionWorkerOptions { UseStaThread = true });
await worker.InitializeAsync();
using var engine = new WkHtmlToXEngine(worker, ownsWorker: true);
4. ProgressChangedEventArgs now carries the numeric progress
The progress integer reported by the native callback used to be ignored. It is now surfaced on the event args.
// v10.x
new ProgressChangedEventArgs(document, description);
// v11.0.0
new ProgressChangedEventArgs(document, progress, description);
configuration.ProgressChangedAction = args =>
Console.WriteLine($"{args.Progress}% - {args.Description}");
If you subclassed ProgressChangedEventArgs or constructed it yourself
(mocks, custom tests), update the constructor call-sites.
5. Removed types
The following internals are gone - delete any references from consumer code or CI filters:
SafeLibraryHandle,LoadLibraryFlags,SystemPosixNativeMethods,SystemWindowsNativeMethods- replaced byAdaskoTheBeAsT.Interop.Unmanaged.UnmanagedLibrary.AssemblyInfo.cs- attributes are emitted by the SDK.azure-pipelines.yml,.runsettings, legacy.sln- replaced by GitHub Actions,coverage.settings.xmland.slnx.
6. Observability
The new execution worker emits ActivitySource / Meter telemetry
named AdaskoTheBeAsT.Interop.Execution. Hook it into OpenTelemetry
with two lines:
builder.Services.AddOpenTelemetry()
.WithTracing(t => t.AddSource("AdaskoTheBeAsT.Interop.Execution"))
.WithMetrics(m => m.AddMeter("AdaskoTheBeAsT.Interop.Execution"));
7. See also
docs/plan.md- the migration plan this release implements.docs/adr/- architecture decision records for every step.
๐๏ธ Architecture
- Clean Separation - Abstractions, Engine, Loaders, Modules, and Settings
- Resource Management - Proper
IDisposableimplementation with thread-safe disposal - Cross-Platform Loading - Platform-specific library loaders (Windows, Linux, macOS)
- Worker Thread Pattern -
ExecutionWorker<WkHtmlToXSession>fromAdaskoTheBeAsT.Interop.Executionowns the STA thread, queue, startup handshake, fault signalling and session recycling - Visitor Pattern - Extensible work item processing
๐งช Code Quality
This project maintains high code quality standards with:
- 20+ static code analyzers (Roslyn, SonarAnalyzer, StyleCop, etc.)
- Comprehensive test coverage (unit, integration, memory tests)
- Strict null reference checks enabled
- Treats warnings as errors
- Continuous Integration with GitHub Actions
- SonarCloud / SonarQube quality gate
๐ค Contributing
Contributions are welcome! Please ensure:
- Code follows existing patterns and conventions
- All tests pass
- Code analyzers produce no warnings
- XML documentation is provided for public APIs
๐ License
This project is licensed under the terms specified in the LICENSE file.
๐ Acknowledgments
This library is built upon DinkToPdf with a completely reworked interoperability layer to address memory management and thread-safety concerns.
๐ Additional Resources
- wkhtmltopdf Documentation
- Sample Projects - Console, Web API, and OWIN examples
- Issue Tracker
๐ก Tips & Best Practices
- Use the DI / Hosting packages - prefer
AddWkHtmlToXHostedService(orAddWkHtmlToXfor non-hosted apps) over newing up the engine - Singleton engine - never construct more than one
WkHtmlToXEnginein a process; the worker serializes native calls - Session recycling - set
MaxOperationsPerSessionfor long-running processes to periodically reset native state - Memory Management - Use
RecyclableMemoryStreamfor high-throughput scenarios - Cancellation - Always pass
CancellationTokenfor responsive cancellation - Linux Deployment - Ensure correct runtime identifier for your Linux distribution
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. 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. |
| .NET Framework | net462 is compatible. net463 was computed. net47 is compatible. net471 is compatible. net472 is compatible. net48 is compatible. net481 is compatible. |
-
.NETFramework 4.6.2
- AdaskoTheBeAsT.Interop.Execution (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.DependencyInjection (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.Hosting (>= 1.0.0)
- AdaskoTheBeAsT.WkHtmlToX (>= 11.1.0)
- AdaskoTheBeAsT.WkHtmlToX.DependencyInjection (>= 11.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6 && < 11.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.6 && < 11.0.0)
-
.NETFramework 4.7
- AdaskoTheBeAsT.Interop.Execution (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.DependencyInjection (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.Hosting (>= 1.0.0)
- AdaskoTheBeAsT.WkHtmlToX (>= 11.1.0)
- AdaskoTheBeAsT.WkHtmlToX.DependencyInjection (>= 11.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6 && < 11.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.6 && < 11.0.0)
-
.NETFramework 4.7.1
- AdaskoTheBeAsT.Interop.Execution (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.DependencyInjection (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.Hosting (>= 1.0.0)
- AdaskoTheBeAsT.WkHtmlToX (>= 11.1.0)
- AdaskoTheBeAsT.WkHtmlToX.DependencyInjection (>= 11.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6 && < 11.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.6 && < 11.0.0)
-
.NETFramework 4.7.2
- AdaskoTheBeAsT.Interop.Execution (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.DependencyInjection (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.Hosting (>= 1.0.0)
- AdaskoTheBeAsT.WkHtmlToX (>= 11.1.0)
- AdaskoTheBeAsT.WkHtmlToX.DependencyInjection (>= 11.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6 && < 11.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.6 && < 11.0.0)
-
.NETFramework 4.8
- AdaskoTheBeAsT.Interop.Execution (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.DependencyInjection (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.Hosting (>= 1.0.0)
- AdaskoTheBeAsT.WkHtmlToX (>= 11.1.0)
- AdaskoTheBeAsT.WkHtmlToX.DependencyInjection (>= 11.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6 && < 11.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.6 && < 11.0.0)
-
.NETFramework 4.8.1
- AdaskoTheBeAsT.Interop.Execution (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.DependencyInjection (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.Hosting (>= 1.0.0)
- AdaskoTheBeAsT.WkHtmlToX (>= 11.1.0)
- AdaskoTheBeAsT.WkHtmlToX.DependencyInjection (>= 11.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6 && < 11.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.6 && < 11.0.0)
-
net10.0
- AdaskoTheBeAsT.Interop.Execution (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.DependencyInjection (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.Hosting (>= 1.0.0)
- AdaskoTheBeAsT.WkHtmlToX (>= 11.1.0)
- AdaskoTheBeAsT.WkHtmlToX.DependencyInjection (>= 11.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.6 && < 11.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.6 && < 11.0.0)
-
net8.0
- AdaskoTheBeAsT.Interop.Execution (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.DependencyInjection (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.Hosting (>= 1.0.0)
- AdaskoTheBeAsT.WkHtmlToX (>= 11.1.0)
- AdaskoTheBeAsT.WkHtmlToX.DependencyInjection (>= 11.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2 && < 9.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1 && < 9.0.0)
-
net9.0
- AdaskoTheBeAsT.Interop.Execution (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.DependencyInjection (>= 1.0.0)
- AdaskoTheBeAsT.Interop.Execution.Hosting (>= 1.0.0)
- AdaskoTheBeAsT.WkHtmlToX (>= 11.1.0)
- AdaskoTheBeAsT.WkHtmlToX.DependencyInjection (>= 11.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.15 && < 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.15 && < 10.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.