HTML to PDF in .NET Core: libraries, Razor views, and Docker
The best free way to convert HTML to PDF in .NET Core is PuppeteerSharp driving headless Chrome; commercial libraries like IronPDF and Syncfusion bundle essentially the same engine behind a license fee, and in containerized deployments an HTML to PDF API keeps Chromium out of your image entirely. What you should not do in 2026 is reach for DinkToPdf or NReco.PdfGenerator, the wkhtmltopdf wrappers that still rank in every search.
The ".NET Core" in your search says something specific: you're probably deploying to Linux, likely in a container, possibly serverless. That context is exactly where the old answers fall apart: Windows-only tricks don't exist here, wrapper packages need native binaries your base image doesn't have, and a full Chromium install triples your image size.
This guide is organized around that reality: the free route with the Dockerfile that actually works, the Razor-view pattern for invoice-shaped documents, the wrapper trap, a fair word on the commercial libraries, and the API route for when the container math wins. (For the broader language-level comparison independent of deployment, our C# HTML-to-PDF guide is the companion piece.)
Key TakeawaysPuppeteerSharp (v25, tracking Puppeteer) is the free default: real headless Chrome from C#, full modern CSS and JavaScript.The classic "free" answers are traps: DinkToPdf last shipped in 2017 and NReco.PdfGenerator wraps the same wkhtmltopdf engine that was archived in 2023 with an unpatched 9.8-severity SSRF CVE.Razor views are your PDF templates: render a view to an HTML string, then feed any engine; the pattern is ~20 lines and reuses your existing layouts.Containers change the math: Chromium adds hundreds of MB and a list of native dependencies to your image; an API keeps the image slim and the render off your pods.Commercial libraries buy convenience, not a different engine: most bundle Chromium; you're paying per-developer for packaging and support.
How do I convert HTML to PDF in .NET Core?
Install PuppeteerSharp, download a browser once at startup, load your HTML, and call PdfAsync. That's the whole free path for modern HTML. The alternatives: a commercial library (same engine, plus license), or an HTTP call to a rendering API (no browser in your deployment at all). Wrapper packages around wkhtmltopdf still appear in search results and should be treated as legacy.
PuppeteerSharp: free Chrome rendering in ASP.NET Core
PuppeteerSharp is the maintained .NET port of Puppeteer (v25 as of July 2026):
// dotnet add package PuppeteerSharp
using PuppeteerSharp;
await new BrowserFetcher().DownloadAsync(); // once, at startup
await using var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox" } // required in most containers
});
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html, new NavigationOptions
{ WaitUntil = new[] { WaitUntilNavigation.Networkidle0 } });
var pdfBytes = await page.PdfDataAsync(new PdfOptions
{
Format = PuppeteerSharp.Media.PaperFormat.A4,
PrintBackground = true
});
In a real service, launch one browser and reuse pages per request rather than launching per call, and recycle the browser after a few hundred renders to contain memory growth.
Rendering a Razor view to PDF
The enterprise use case is rarely a raw HTML string; it's "this invoice view, as a PDF." Render the view to a string first, then feed it to any engine. In ASP.NET Core that means asking IRazorViewEngine to render the view into a StringWriter:
public async Task<string> RenderViewAsync<TModel>(string viewName, TModel model)
{
var actionContext = new ActionContext(httpContextAccessor.HttpContext!,
new RouteData(), new ActionDescriptor());
var view = razorViewEngine.FindView(actionContext, viewName, isMainPage: true).View
?? throw new InvalidOperationException($"View '{viewName}' not found");
await using var writer = new StringWriter();
var viewContext = new ViewContext(actionContext, view,
new ViewDataDictionary<TModel>(new EmptyModelMetadataProvider(),
new ModelStateDictionary()) { Model = model },
new TempDataDictionary(actionContext.HttpContext, tempDataProvider),
writer, new HtmlHelperOptions());
await view.RenderAsync(viewContext);
return writer.ToString();
}
The returned string html feeds either engine unchanged, and your existing layout, partials, and CSS come along for free. That's the entire appeal: one Razor template serves the web page and the PDF.
The Dockerfile that actually works
Stock mcr.microsoft.com/dotnet/aspnet images can't run Chromium: the native libraries aren't there. The workable pattern installs them explicitly:
FROM mcr.microsoft.com/dotnet/aspnet:8.0
RUN apt-get update && apt-get install -y \
libnss3 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 \
libxcomposite1 libxdamage1 libxrandr2 libgbm1 libasound2 \
fonts-liberation \
&& rm -rf /var/lib/apt/lists/*
# BrowserFetcher downloads Chromium at startup, or bake it in during build
Expect the browser plus dependencies to add several hundred MB to the image, and note --no-sandbox in the launch args: container runtimes typically don't provide the kernel features Chrome's sandbox wants. Alpine images are a known pain (musl vs glibc); stay on Debian-based tags. Our PuppeteerSharp guide digs further into production configuration.
Avoid the wkhtmltopdf wrappers (DinkToPdf, NReco)
Every ".NET Core html to pdf" search surfaces DinkToPdf and NReco.PdfGenerator, usually labeled "free and easy." The facts: DinkToPdf's last release was 2017, and both wrap wkhtmltopdf, archived January 2023, frozen at a 2016-era WebKit, carrying an unpatched 9.8-severity SSRF (CVE-2022-35583). On the practical side, both need the native wkhtmltopdf library present per-platform, which is its own misery in slim containers, and the engine mangles flexbox and grid regardless.
Free isn't the issue; frozen is. If a wrapper is in your codebase today, our wkhtmltopdf alternatives guide maps the migration; the Razor pattern above means the template layer usually survives the move untouched.
Commercial libraries: what the license buys
IronPDF, Syncfusion, SelectPdf, and Aspose all sell .NET HTML-to-PDF. A fair summary: most bundle a Chromium-family engine (check each vendor's docs), so output quality is in the same family as PuppeteerSharp; the fee (typically hundreds to low thousands per developer per year, check current pricing, it moves) buys packaged deployment (no BrowserFetcher, no Dockerfile surgery), support contracts, and adjacent features like office-format conversion.
That's a legitimate trade for teams that want a vendor on the hook. It is not a different rendering capability, and the per-developer license math should be compared against both the free route's ops cost and the API route's per-document cost before anyone signs.
The API route: no Chromium in your container
The third option moves rendering out of your deployment entirely:
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new("Bearer", Environment.GetEnvironmentVariable("TRANSFORMY_API_KEY"));
var response = await client.PostAsJsonAsync("https://api.transformy.io/v1/pdf/chrome", new
{
html, // your rendered Razor view string
page_size = "A4",
margin = "20mm 15mm",
footer = new { html = "<span style=\"font-size:9px\">Page {{page}} of {{pages}}</span>" }
});
response.EnsureSuccessStatusCode();
var pdfBytes = await response.Content.ReadAsByteArrayAsync();
return File(pdfBytes, "application/pdf", "invoice.pdf"); // in a controller
What changes operationally: the base aspnet image stays as-is (no native deps, no browser, no --no-sandbox), pods stop carrying Chromium's memory profile, and Azure Functions or Lambda deployments stop fighting size limits and cold-start weight. Rendering still happens on real headless Chrome, server-side, with headers/footers and page numbers as parameters; the HTML to PDF API docs cover the full surface including async jobs for batch runs.
License math, honestly: a per-developer commercial license runs hundreds-to-thousands annually per seat; Transformy is free for 100 documents a month, then $99/month for 10,000 ($0.01 per extra). Which wins depends entirely on your volume and team size; now you have the numbers to run it.
FAQ
What's the best free HTML to PDF library for .NET Core?
PuppeteerSharp. It's actively maintained, renders with real headless Chrome, and handles modern CSS and JavaScript. Its cost is operational (browser processes, container deps) rather than monetary. Avoid DinkToPdf and other wkhtmltopdf wrappers despite their "free" label.
How do I convert a Razor view to PDF?
Render the view to an HTML string first (via IRazorViewEngine or a view-rendering helper), then pass that string to any engine: PuppeteerSharp's SetContentAsync, or an API's html field. Your layouts and CSS carry over unchanged.
Can I run HTML to PDF on Azure Functions or AWS Lambda in .NET?
Yes, with caveats: self-hosted Chromium fights size limits and cold starts on serverless .NET. Practical setups either use a container-image function with the dependencies baked in, or skip the problem by calling a rendering API, which needs nothing beyond HttpClient.
Does this work on .NET 8 and newer?
Yes, all three routes. PuppeteerSharp targets modern .NET and tracks current Chromium; the Razor-rendering pattern uses standard ASP.NET Core services unchanged since .NET 6; and the API route is plain HttpClient. It's the wrapper packages that lag: DinkToPdf predates .NET Core 3, let alone .NET 8.
Is DinkToPdf still safe to use?
No for anything touching untrusted HTML, and unwise generally: its last release was 2017 and it wraps an engine archived in 2023 with an unpatched critical CVE. Migrate to PuppeteerSharp or an API; templates usually port with minor CSS fixes.
Conclusion
The .NET Core HTML to PDF decision, compressed:
| Situation | Route | Monthly cost shape |
|---|---|---|
| Free, self-hosted, modern HTML | PuppeteerSharp + the Dockerfile above | Ops time + bigger image |
| Invoice-shaped documents | Razor view → string → either engine | Same as chosen engine |
| Existing DinkToPdf/NReco code | Migrate (archived engine) | One-time port |
| Vendor support required | Commercial library | Per-developer license |
| Slim containers, serverless, spiky volume | Rendering API | Per-document (free tier first) |
- Free and self-hosted → PuppeteerSharp, with the Dockerfile above and a pooled browser.
- Invoice-shaped documents → the Razor-view-to-string pattern feeding either engine.
- Wrapper packages (DinkToPdf, NReco) → migrate; the engine under them is archived.
- Commercial libraries → fine, if the per-seat fee beats your ops cost and volume math.
- Slim containers, serverless, spiky volume → the API route; your image stays browser-free.
The fastest way to know which side of the math you're on: render your heaviest real Razor view both ways. A free Transformy key covers 100 documents a month with unlimited watermarked test renders, which is more than enough to settle it.