/// ENGINEERING

HTML to PDF in C# — Three Ways Compared (.NET)

Convert HTML to PDF in C#: wkhtmltopdf wrappers, headless Chromium with PuppeteerSharp, or a REST API. Code for each, and when to pick which.

Document automation engineers, ASHDOCS
Published:
Last updated:

Generating PDFs from HTML is one of those .NET tasks that looks solved until you're the one shipping it. Invoices, order confirmations, reports — the HTML already exists; the question is which rendering engine turns it into a faithful PDF, and what that engine costs you to operate.

Way 1 — wkhtmltopdf wrappers (DinkToPdf and friends)

The old workhorse: a WebKit-based CLI with .NET wrappers.

var converter = new SynchronizedConverter(new PdfTools());
var doc = new HtmlToPdfDocument {
    GlobalSettings = { PaperSize = PaperKind.A4 },
    Objects = { new ObjectSettings { HtmlContent = html } }
};
byte[] pdf = converter.Convert(doc);

Upside: free, offline, battle-tested. Downside: the underlying engine froze years ago — its WebKit predates flexbox and grid as you know them, so modern CSS silently breaks. Native-library deployment on Linux containers is its own small hobby. Choose it only for simple, table-based layouts you control completely.

Way 2 — Headless Chromium (PuppeteerSharp / Playwright for .NET)

Run a real browser and print:

await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html, new NavigationOptions { WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } });
byte[] pdf = await page.PdfAsync(new PdfOptions { Format = PaperFormat.A4, PrintBackground = true });

Upside: perfect modern-CSS fidelity — it is Chrome. Downside: you now operate a browser fleet: ~300–500 MB memory per instance, zombie processes to reap, Chromium security patches to track, and container images that balloon. Fine for low volume on infrastructure you already babysit.

Way 3 — REST API

Move the browser to someone else's fleet:

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
var payload = JsonSerializer.Serialize(new {
    html = "<h1>Invoice #1042</h1>",
    options = new { format = "A4", printBackground = true }
});
var resp = await client.PostAsync(
    "https://www.ashdocs.com/api/v1/tools/html-to-pdf",
    new StringContent(payload, Encoding.UTF8, "application/json"));
byte[] pdf = await resp.Content.ReadAsByteArrayAsync();

Upside: Chromium fidelity with zero Chromium ops; scales without capacity planning; works identically from an Azure Function, a container, or a background worker. Downside: per-conversion cost and a network dependency — mitigate the latter with retries and an idempotency key.

Picking one

Static, simple markup and zero budget → wkhtmltopdf. Modern CSS, low volume, existing browser ops → PuppeteerSharp. Modern CSS at production volume, or a team that would rather not patch Chromium → the API. A useful tiebreak: if PDF generation is your product, own the fleet; if it's a feature, rent it.

FAQ

Why does my C# HTML to PDF output look different from the browser? Almost always the rendering engine: wkhtmltopdf's WebKit predates modern flexbox/grid. Rendering through actual Chromium (locally or via API) makes output match Chrome's print preview.

Can I generate PDFs in Azure Functions? Headless Chromium inside Functions is possible but fights cold starts and memory caps. An HTTP call to a conversion API sidesteps both.

How do I keep backgrounds and colors? Set printBackground: true (API/Puppeteer) — the print pipeline strips backgrounds by default.

Is there a free way to test? The free tier includes 100 credits monthly, and sandbox keys render without consuming credits.

First PDF in five minutes — get a free API key.