DEV Community

IronSoftware
IronSoftware

Posted on

PDF Generation in .NET Core with C# (Guide)

.NET Core doesn't include built-in PDF generation capabilities. Unlike some frameworks that bundle PDF libraries, Microsoft left PDF functionality to third-party packages. This means every .NET Core project needing PDF output — invoice systems, report generators, receipt services — requires choosing and integrating a PDF library.

I've tried most .NET PDF libraries over the years. iTextSharp was my first choice because it appeared in every Stack Overflow answer. The problem is iTextSharp doesn't actually convert HTML to PDF directly — it builds PDFs programmatically through code. You create document objects, add paragraphs, manage fonts, calculate layouts manually. The HTML add-on exists but uses AGPL licensing and has a complex API. Generating a simple invoice required 100+ lines of PDF construction code.

Then came wkhtmltopdf wrappers like DinkToPdf and NReco. These finally worked for HTML-to-PDF conversion, but wkhtmltopdf itself is unmaintained since 2019. It's based on Qt WebKit from 2015, meaning modern CSS features don't work. Flexbox is buggy. Grid layouts fail entirely. JavaScript support stops at ES5. For .NET Core applications targeting modern browsers and using current CSS, wkhtmltopdf produces broken PDFs.

PDFSharp is another common recommendation. It's open source and lightweight, but it doesn't handle HTML at all — you're drawing text and shapes at coordinates. This works for simple labels or forms but becomes unmaintainable for complex documents. Changing an invoice layout means recalculating dozens of coordinate positions in code rather than editing HTML templates.

IronPDF solved these problems by using Chromium's rendering engine. If your HTML renders correctly in Chrome, it generates correctly as a PDF. Modern CSS works (Flexbox, Grid, transforms), JavaScript executes (charts, dynamic content), and web fonts load properly. The API is purpose-built for .NET — no subprocess management, no external dependencies, just NuGet install and method calls.

I migrated a .NET Core invoice system from DinkToPdf to IronPDF and eliminated weeks of "PDF debugging" where invoices looked correct in browsers but broke in PDFs. The Chromium engine renders identically to Chrome, so what stakeholders see in browser previews is exactly what appears in generated PDFs. This predictability is critical for production systems generating thousands of documents daily.

using IronPdf;
// Install via NuGet: Install-Package IronPdf

var renderer = new [ChromePdfRenderer](https://ironpdf.com/blog/videos/how-to-render-html-string-to-pdf-in-csharp-ironpdf/)();
var html = "<h1>Invoice #12345</h1><p>Total: $1,234.56</p>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("invoice.pdf");
Enter fullscreen mode Exit fullscreen mode

That's the fundamental pattern for .NET Core PDF generation — create a renderer, pass HTML, save the PDF. The renderer handles Chromium initialization, HTML parsing, CSS application, and PDF construction. For applications generating many PDFs, create one renderer instance and reuse it to amortize initialization costs.

Why Do Developers Need PDF Generation in .NET Core?

PDF generation is fundamental to business applications. Every SaaS product eventually needs PDF export for invoices, receipts, reports, contracts, or documentation. The challenge is that .NET Core doesn't provide PDF capabilities out of the box, so developers must integrate third-party libraries.

The common use cases I encounter are invoice generation for accounting systems, receipt delivery for e-commerce platforms, report export for dashboards and analytics, contract generation for document management, and documentation archival for compliance. These all share a pattern: dynamic data from databases or APIs needs to become formatted PDF documents for download, email, or storage.

HTML-to-PDF conversion is the most practical approach because HTML is a mature document formatting language. Developers already know HTML and CSS. Designers can create templates without learning PDF APIs. Changes to document layouts happen in HTML files rather than code. This separation of concerns — data in C#, presentation in HTML — makes systems maintainable.

The alternative is programmatic PDF construction where you write code to create PDF objects directly. This works for simple documents but becomes unmaintainable for complex layouts. I've inherited systems using iTextSharp for invoice generation where changing the layout required modifying 200+ lines of PDF construction code. Nobody wanted to touch it. Migrating to HTML templates made the system maintainable again.

What Are the Main PDF Libraries for .NET Core?

The .NET ecosystem has several PDF libraries, each with different capabilities and trade-offs.

iTextSharp / iText7 is widely used for PDF manipulation — merging PDFs, extracting text, adding annotations. It's powerful for these tasks but HTML-to-PDF conversion requires the pdfHTML add-on which uses AGPL licensing (copyleft) or expensive commercial licenses. The API is complex even for simple conversions. I use iTextSharp when I need specific PDF manipulation features unavailable elsewhere, not for HTML conversion.

wkhtmltopdf wrappers (DinkToPdf, NReco.PdfGenerator) were the standard solution before Chromium-based options existed. The problem is wkhtmltopdf itself is unmaintained and based on WebKit from 2015. Modern CSS features don't work. Security vulnerabilities accumulate with no patches. It's a dead project. I actively migrate systems away from wkhtmltopdf because it's becoming incompatible with modern web development practices.

PDFSharp is an open-source library for creating PDFs programmatically. It doesn't handle HTML — you draw shapes and text at coordinates. This is useful for labels, barcodes, or simple forms but impractical for complex documents. I've used PDFSharp for generating shipping labels where precise positioning matters, not for invoices or reports.

QuestPDF is a newer open-source option using a fluent API for building PDFs programmatically. It's more intuitive than PDFSharp but still requires code for layout rather than HTML templates. Good for programmatic document generation where layouts are dynamic, less suited for template-based workflows.

IronPDF uses Chromium's rendering engine for HTML-to-PDF conversion. It supports modern web standards because it uses the same engine as Chrome. The API is .NET-native with no external dependencies or subprocess management. For .NET Core applications needing HTML-to-PDF conversion, this is my default choice.

How Do I Install IronPDF in .NET Core?

IronPDF installs via NuGet Package Manager. In Visual Studio, use the Package Manager Console:

Install-Package IronPdf
Enter fullscreen mode Exit fullscreen mode

Or using the .NET CLI:

dotnet add package IronPdf
Enter fullscreen mode Exit fullscreen mode

The package includes the Chromium rendering engine, so there are no additional runtime dependencies to install. This makes deployment simpler — the NuGet package contains everything needed for PDF generation.

For .NET Core 3.1, .NET 5, .NET 6, .NET 8, .NET 9, and .NET 10 projects, the same package works across all versions. IronPDF targets .NET Standard, ensuring compatibility with current and future .NET releases.

After installation, the basic usage pattern is:

using IronPdf;

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello PDF</h1>");
pdf.SaveAs("output.pdf");
Enter fullscreen mode Exit fullscreen mode

This works identically in console applications, ASP.NET Core web applications, worker services, or Azure Functions. The library has no framework-specific requirements beyond .NET itself.

How Can I Convert a URL to PDF in .NET Core?

Converting live URLs to PDFs is useful for archiving web content, capturing dashboard snapshots, or generating PDFs from existing web applications.

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://example.com/report");
pdf.SaveAs("report.pdf");
Enter fullscreen mode Exit fullscreen mode

The renderer fetches the URL, executes JavaScript, applies CSS, and renders the final page state. This is identical to how Chrome would display the page, ensuring accurate rendering of modern web applications.

For pages with asynchronous content loading — React apps, dashboards with API calls, charts that render after page load — add rendering delays:

renderer.RenderingOptions.WaitFor.RenderDelay(2000);  // Wait 2 seconds
var pdf = renderer.RenderUrlAsPdf("https://example.com/dashboard");
Enter fullscreen mode Exit fullscreen mode

This delays PDF generation for 2 seconds after initial page load, giving JavaScript time to fetch data and render content. I use this for dashboards where the initial HTML is minimal and JavaScript populates everything dynamically.

For more precise control, wait for specific elements:

renderer.RenderingOptions.WaitFor.HtmlElement("#content-loaded");
Enter fullscreen mode Exit fullscreen mode

This waits until an element with ID content-loaded appears in the DOM. Your JavaScript adds this element after async operations complete, signaling the page is ready. More reliable than fixed delays because it responds to actual content state.

How Do I Render Razor Views to PDF in ASP.NET Core?

Razor views are the standard templating engine in ASP.NET Core MVC. Rendering Razor views to PDF lets you use existing MVC patterns — models, layouts, partial views — for document generation.

In an ASP.NET Core controller, render a view to HTML then convert to PDF:

public class InvoiceController : Controller
{
    public IActionResult DownloadInvoice(int id)
    {
        var invoice = GetInvoiceData(id);

        var html = RenderViewToString("InvoiceTemplate", invoice);

        var renderer = new ChromePdfRenderer();
        var pdf = renderer.RenderHtmlAsPdf(html);

        return File(pdf.BinaryData, "application/pdf", $"invoice-{id}.pdf");
    }

    private string RenderViewToString(string viewName, object model)
    {
        ViewData.Model = model;
        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngine.FindView(ControllerContext, viewName, false);
            var viewContext = new ViewContext(
                ControllerContext, viewResult.View, ViewData,
                TempData, sw, new HtmlHelperOptions());

            viewResult.View.RenderAsync(viewContext).Wait();
            return sw.ToString();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This pattern separates concerns cleanly. The Razor view handles presentation — HTML structure, CSS styling, data formatting. The controller handles business logic — fetching data, permissions, PDF generation. Changes to invoice designs happen in Razor templates without code changes.

For complex documents, use Razor layouts and partial views:

<!-- _InvoiceLayout.cshtml -->
<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: Arial; margin: 40px; }
        .header { color: #333; font-size: 24px; }
    </style>
</head>
<body>
    @RenderBody()
</body>
</html>

<!-- InvoiceTemplate.cshtml -->
@model InvoiceViewModel
@{
    Layout = "_InvoiceLayout";
}

<div class="header">Invoice #@Model.Number</div>
<p>Customer: @Model.CustomerName</p>
<p>Total: @Model.Total.ToString("C")</p>
Enter fullscreen mode Exit fullscreen mode

Razor's full feature set works — model binding, layout inheritance, partial views, HTML helpers. This makes PDF templates as maintainable as web pages.

What About Docker and Linux Deployment?

IronPDF works cross-platform on Windows, Linux, and macOS. For containerized .NET Core applications running in Docker, IronPDF supports Linux environments without additional configuration.

For Docker deployments, use a Dockerfile that includes necessary dependencies:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app

# Install dependencies for Chromium
RUN apt-get update && apt-get install -y \
    libgdiplus \
    libc6-dev \
    && rm -rf /var/lib/apt/lists/*

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp/MyApp.csproj", "MyApp/"]
RUN dotnet restore "MyApp/MyApp.csproj"
COPY . .
WORKDIR "/src/MyApp"
RUN dotnet build "MyApp.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]
Enter fullscreen mode Exit fullscreen mode

The apt-get install step adds libraries required for Chromium's rendering engine on Linux. This handles font rendering and graphics operations. I've deployed .NET Core applications with IronPDF to Azure Container Instances, AWS ECS, and Kubernetes clusters using this Dockerfile pattern.

For Azure App Service on Linux, the platform includes necessary libraries by default. IronPDF works without additional configuration. For AWS Lambda or serverless environments, use container-based Lambda functions with the dependencies installed.

Does IronPDF Work with .NET 10 and .NET 9?

Yes, IronPDF supports all modern .NET versions including .NET 10, .NET 9, .NET 8, .NET 6, and .NET Core 3.1. The library targets .NET Standard 2.0, ensuring compatibility with current and future .NET releases.

I've used IronPDF across .NET version migrations — from .NET Core 3.1 to .NET 5, then .NET 6, .NET 8, and now .NET 9. The code remains unchanged. Updating NuGet packages brings compatibility with new .NET versions without modifying PDF generation logic.

For new projects starting on .NET 10, the same installation and usage patterns apply:

dotnet new webapi -n MyPdfApi
cd MyPdfApi
dotnet add package IronPdf
Enter fullscreen mode Exit fullscreen mode

The API surface is identical across .NET versions. Code written for .NET Core 3.1 runs unchanged on .NET 10. This forward compatibility simplifies long-term maintenance — you're not rewriting PDF generation code with each .NET upgrade.

What's the Performance Like?

PDF generation with IronPDF typically takes 100-300ms per document for standard invoices or reports. Complex documents with charts, images, or heavy JavaScript take 500ms-1s. This is 3-5x faster than browser automation tools like Playwright because IronPDF uses the Chromium rendering engine directly without browser overhead.

For batch processing, use async methods:

var renderer = new ChromePdfRenderer();
var tasks = new List<Task<PdfDocument>>();

foreach (var invoice in invoices)
{
    var html = GenerateInvoiceHtml(invoice);
    var task = renderer.RenderHtmlAsPdfAsync(html);
    tasks.Add(task);
}

var pdfs = await Task.WhenAll(tasks);
Enter fullscreen mode Exit fullscreen mode

This processes multiple PDFs concurrently, utilizing available CPU cores. I've generated 5,000 invoices in 8 minutes using this pattern — roughly 10 PDFs per second on a 4-core server.

The trade-off compared to simpler libraries like PDFSharp is that Chromium rendering consumes more resources. PDFSharp might generate PDFs in 50ms, but you're writing coordinate-based drawing code. IronPDF's 200ms generation time includes full HTML/CSS/JavaScript rendering, which is worth the overhead for maintainable template-based workflows.

Quick Reference

Task Code Notes
Install package dotnet add package IronPdf NuGet installation
HTML to PDF renderer.RenderHtmlAsPdf(html) Basic conversion
URL to PDF renderer.RenderUrlAsPdf(url) Live websites
Razor to PDF Render view to string, then RenderHtmlAsPdf() MVC integration
Async generation renderer.RenderHtmlAsPdfAsync(html) Batch processing
Rendering delay RenderingOptions.WaitFor.RenderDelay(ms) For JavaScript
Wait for element RenderingOptions.WaitFor.HtmlElement("#id") Precise timing

Key Principles:

  • IronPDF uses Chromium engine for modern HTML/CSS/JavaScript support
  • Works cross-platform (Windows, Linux, macOS, Docker)
  • Compatible with .NET 10, .NET 9, .NET 8, .NET 6, .NET Core 3.1
  • Faster than browser automation (Playwright), easier than programmatic construction (PDFSharp)
  • Template-based workflow more maintainable than code-based PDF building
  • Avoid wkhtmltopdf (unmaintained, frozen WebKit from 2015)

The complete .NET Core PDF tutorial covers advanced scenarios including headers/footers, security settings, and cloud deployment patterns.


Written by Jacob Mellor, CTO at Iron Software. Jacob created IronPDF and leads a team of 50+ engineers building .NET document processing libraries.

Top comments (0)