If your .NET app still calls DinkToPdf, NReco.PdfGenerator, Rotativa, or WkHtmlToXSharp in production, you're shipping a wrapper around a binary that hasn't had a security patch in years — and one of its open holes is a CVSS 9.8.
wkhtmltopdf's repository has been frozen and read-only since January 2023, and the whole project org was archived by mid-2024. Development has stopped; nobody is accepting patches. The underlying QtWebKit engine forked from upstream around 2012 — no modern Flexbox, no Grid, no modern JavaScript, no ICU updates. And , a .NET PDF library, and the code below is SelectPdf because that's what I can vouch for line by line. I'll be honest about where it fits and where it doesn't — including a Windows/Linux fork you need to know about before you write any code. The broader point stands no matter what you pick: stop wrapping the dead binary.
First, the honest part: Windows or Linux?
I'm putting this at the top because it decides everything downstream, and nothing annoys an engineer more than finding it in paragraph 30.
If you deploy to Windows (App Service on Windows, Windows containers, a VM, IIS) — the SelectPdf library is a direct NuGet reference: one synchronous API call, no separate service to stand up. This whole guide applies. Keep reading.
If you deploy to Linux (Linux containers, Linux App Service, AKS) — the library is Windows-only. Don't fight it. The cross-platform answer is SelectPdf's ). TheChromium.Windowsruntime packages are AnyCPU (they run in both x86 and x64 workers); the commercial library additionally ships.x64-pinned variants, the CE does not. On .NET Framework the same packages work — SelectPdf still targetsnet20,net40,net461,net472, andnetstandard2.0, so migrating doesn't force a TFM bump.
Migrating your wkhtmltopdf flags
The
ConvertHtmlStringcall is the easy 20%. The part that actually took you an afternoon in wkhtmltopdf was the CLI flags — headers, footers, page numbers, margins. Here's the mapping so you're not reverse-engineering it:
wkhtmltopdf
SelectPdf
--page-size A4
Options.PdfPageSize = PdfPageSize.A4
--orientation Landscape
Options.PdfPageOrientation = PdfPageOrientation.Landscape
--margin-top 20(etc.)
Options.MarginTop = 20(MarginBottom/Left/Right)
--header-html header.html
Options.DisplayHeader = true;thenconverter.Header.Add(new PdfHtmlSection(url))
--footer-html footer.html
Options.DisplayFooter = true;thenconverter.Footer.Add(...)
[page] / [topage]footer tokens
{page_number}/{total_pages}in aPdfTextSection
--print-media-type
Options.CssMediaType = HtmlToPdfCssMediaType.Print
Footer with page numbers, end to end:
CODEconverter.Options.DisplayFooter = true;
converter.Footer.Height = 40;
var pageNo = new PdfTextSection(0, 10,
"Page {page_number} of {total_pages}",
new System.Drawing.Font("Arial", 9));
pageNo.HorizontalAlign = PdfTextHorizontalAlign.Right;
converter.Footer.Add(pageNo);
That — headers, footers, page numbering — all works in the free Community Edition too, within the 5-page cap.
The Azure App Service angle
This is the one I get asked about most, so I'll be explicit.
Azure App Service runs your app in a Windows sandbox that blocks most User32/GDI32 (Win32k.sys) system calls — exactly what a GDI-based browser engine bundled in a .NET app needs. That's what makes wkhtmltopdf flaky there: P/Invoke load failures, missing GDI calls, font issues. Bundled headless-Chrome setups tend to struggle in the same sandbox too, for related reasons (process/GPU restrictions rather than GDI specifically).
SelectPdf's Chromium engine — a CEF/Chromium backend introduced in v26.2, on Chromium 148 as of v26.3 — is built to run inside that sandbox. The same code you run on your dev machine runs unchanged on Azure App Service (Basic plan and above), and on Azure Functions when hosted on a Premium or App Service plan (not Consumption or Free/Shared). From your code it's the one line you already saw:
CODEvar converter = new HtmlToPdf();
converter.Options.RenderingEngine = RenderingEngine.Chromium;
PdfDocument doc = converter.ConvertUrl("https://example.com/report");
doc.Save("report.pdf");
doc.Close();
No "but it works locally" Slack thread at 11pm.
Closing the SSRF loop
We opened on CVE-2022-35583, so let's not hand-wave the fix. Two things change when you migrate:
You get a kill-switch for local-file exfiltration. SetHtmlToPdfOptions.DenyLocalFileAccess = trueper conversion, orGlobalProperties.ForceDenyLocalFileAccess = trueprocess-wide, andfile://reads are blocked — shutting off the local-file-read half of the classic wkhtmltopdf attack.
The internal-IP SSRF half is a network problem, and the right answer depends on trust. If you convert genuinely untrusted, user-supplied HTML, don't render it on your own infrastructure next to your internal network at all — push it to the
- Live demo (paste any URL):
- Docs:
If this was useful, follow — the next post is "Top 10 HTML-to-PDF Libraries for C# / .NET (2026): A Practical Comparison," a fair head-to-head of the real options in this space, including where SelectPdf isn't the right pick.
SOCIAL SHARE CARD GENERATOR