DEV Community

IronSoftware
IronSoftware

Posted on

HTML String to PDF in C# (.NET 10 Guide)

Converting HTML strings to PDF enables dynamic document generation — invoices populated from database data, reports assembled from templates, certificates personalized per recipient, emails archived as PDFs. I've built systems generating thousands of PDFs daily from HTML templates: billing systems rendering Razor views as invoices, reporting platforms converting HTML charts to PDF dashboards, certificate generators producing personalized completion certificates.

The challenge is choosing a library that actually works. Stack Overflow threads about HTML to PDF are battlegrounds — developers report wkhtmltopdf rendering incorrectly, SelectPdf choking on modern CSS, NReco failing with JavaScript-heavy content, iTextSharp requiring complex XML intermediates. These answers accumulate votes over years despite libraries becoming deprecated, licensing changing, or rendering engines failing with HTML5/CSS3/JavaScript.

The core problem: HTML rendering is extraordinarily complex. Browsers invest billions implementing CSS Grid, Flexbox, Web Fonts, SVG, Canvas, responsive layouts. PDF libraries must replicate this rendering accurately or PDFs look broken. Early libraries used simplified rendering engines — adequate for basic HTML but failing with Bootstrap, Tailwind, modern CSS. wkhtmltopdf uses an outdated WebKit build from 2016, missing 8 years of CSS evolution.

IronPDF uses Chromium — the same rendering engine powering Chrome, Edge, Brave. If HTML displays correctly in Chrome, IronPDF renders it identically in PDF. Test your template in a browser, get pixel-perfect PDF output. This eliminates the trial-and-error debugging cycle where developers tweak CSS hoping PDF rendering improves.

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

var renderer = new ChromePdfRenderer();

var html = @"
<!DOCTYPE html>
<html>
<head>
    <meta charset='UTF-8'>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        h1 { color: #2563eb; }
        .invoice-total { font-size: 24px; font-weight: bold; }
    </style>
</head>
<body>
    <h1>Invoice #12345</h1>
    <p>Date: January 26, 2025</p>
    <p class='invoice-total'>Total: $1,234.56</p>
</body>
</html>
";

var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("invoice.pdf");
Enter fullscreen mode Exit fullscreen mode

This renders an HTML string into a PDF. The HTML includes embedded CSS (<style> tag). Modern CSS features like custom fonts, Grid, Flexbox all render correctly because Chromium supports them.

What NuGet Packages Do I Need?

Install IronPDF via Package Manager Console:

Install-Package IronPdf
Enter fullscreen mode Exit fullscreen mode

Or .NET CLI:

dotnet add package IronPdf
Enter fullscreen mode Exit fullscreen mode

IronPDF bundles Chromium rendering engine — no external dependencies, no browser installations, no system configuration.

How Do I Handle External CSS and Images?

HTML strings often reference external resources — CSS files, images, fonts. Without context, the renderer can't locate these files. Set BaseUrlOrPath to resolve relative URLs:

var html = @"
<!DOCTYPE html>
<html>
<head>
    <link rel='stylesheet' href='styles.css'>
</head>
<body>
    <img src='logo.png' alt='Company Logo'>
    <h1>Annual Report</h1>
</body>
</html>
";

var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.BaseUrlOrPath = "https://example.com/templates/";

var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report.pdf");
Enter fullscreen mode Exit fullscreen mode

The renderer resolves styles.css as https://example.com/templates/styles.css and logo.png as https://example.com/templates/logo.png. For local files, use file paths:

renderer.RenderingOptions.BaseUrlOrPath = @"C:\Templates\";
Enter fullscreen mode Exit fullscreen mode

Now styles.css resolves to C:\Templates\styles.css.

I generate branded documents this way — HTML templates reference company logos, brand CSS, custom fonts. Setting BaseUrlOrPath to the template directory loads all assets correctly without embedding them in HTML strings.

Can I Embed CSS and Images Directly?

Yes, using inline styles and Data URIs eliminates external dependencies:

var html = @"
<!DOCTYPE html>
<html>
<head>
    <style>
        body { background-color: #f3f4f6; padding: 20px; }
        .card { background: white; border-radius: 8px; padding: 24px; }
    </style>
</head>
<body>
    <div class='card'>
        <img src='data:image/png;base64,iVBORw0KGgo...' alt='Logo'>
        <h1>Self-Contained Document</h1>
    </div>
</body>
</html>
";

var pdf = renderer.RenderHtmlAsPdf(html);
Enter fullscreen mode Exit fullscreen mode

The <style> tag embeds CSS directly. The data:image/png;base64,... URI embeds the image as Base64-encoded data. This creates fully self-contained HTML strings requiring no external resources — useful for emails-to-PDF where external URLs may be blocked.

How Does IronPDF Compare to Other HTML String Renderers?

wkhtmltopdf (via wrappers):

  • Rendering Engine: Outdated WebKit from 2016
  • CSS Support: No Grid, limited Flexbox, broken custom fonts
  • Complexity: Requires wkhtmltopdf.exe installation, managing processes, parsing stderr for errors
  • Status: Deprecated, no updates since 2020
  • Stack Overflow: Still appears in top answers despite deprecation

I migrated from wkhtmltopdf specifically because Bootstrap 5 layouts rendered incorrectly — Grid-based designs collapsed, responsive breakpoints failed. Testing revealed wkhtmltopdf's WebKit couldn't parse modern CSS. IronPDF's Chromium rendered identically to Chrome browser previews.

SelectPdf:

  • Rendering Engine: Proprietary WebKit variant
  • Licensing: Per-server licensing, expensive for cloud deployments
  • Limitations: JavaScript execution limited, some CSS3 features unsupported

NReco.PdfGenerator:

  • Rendering Engine: wkhtmltopdf wrapper
  • Inherits limitations: Same outdated WebKit rendering issues
  • Advantage: Free tier available

iTextSharp/iText7:

  • Approach: XML-based (not HTML rendering)
  • Complexity: Requires converting HTML to XML-DSL first, manually positioning elements
  • Use Case: Programmatic PDF creation, not HTML rendering
  • Licensing: AGPL (viral open-source) or commercial

IronPDF's Chromium engine renders HTML exactly as Chrome does. If you're choosing between libraries, test your actual HTML template in Chrome. If it looks correct, IronPDF will match it. If it looks broken in Chrome, fix the HTML — IronPDF will render the broken version identically.

How Do I Render HTML from Razor Views?

ASP.NET projects using Razor views can render views to HTML strings, then convert to PDF:

// In an ASP.NET Core controller
public async Task<IActionResult> DownloadInvoice(int id)
{
    var invoice = await _db.Invoices.FindAsync(id);

    // Render Razor view to HTML string
    var html = await _viewRenderer.RenderToStringAsync("Invoice", invoice);

    // Convert to PDF
    var renderer = new ChromePdfRenderer();
    var pdf = renderer.RenderHtmlAsPdf(html);

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

This renders a Razor view (with model binding) to HTML, converts to PDF, returns as file download. The pattern enables using Razor's full templating power — loops, conditionals, partial views — while generating PDFs.

I've built invoice systems this way where HTML invoices display in browsers for customer review, with "Download PDF" buttons rendering the same Razor view to PDF. One template, two outputs: HTML for web, PDF for download.

What About JavaScript-Heavy Content?

Chromium executes JavaScript before rendering. Charts generated by Chart.js, tables built by DataTables, dynamic content loaded via fetch() all execute:

var html = @"
<!DOCTYPE html>
<html>
<head>
    <script src='https://cdn.jsdelivr.net/npm/chart.js'></script>
</head>
<body>
    <canvas id='myChart'></canvas>
    <script>
        new Chart(document.getElementById('myChart'), {
            type: 'bar',
            data: { labels: ['Jan', 'Feb', 'Mar'], datasets: [{ data: [10, 20, 30] }] }
        });
    </script>
</body>
</html>
";

var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.WaitFor.RenderDelay(500); // Wait for JS execution

var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("chart.pdf");
Enter fullscreen mode Exit fullscreen mode

The RenderDelay(500) waits 500 milliseconds for JavaScript to execute and charts to render before capturing PDF. Increase delay for complex JavaScript or use WaitFor.NetworkIdle() to wait until all network requests complete.

I generate executive dashboards this way — HTML templates load Chart.js, render interactive charts client-side, IronPDF captures fully-rendered charts in PDFs.

How Do I Handle Very Long HTML Strings?

C# string literals have practical limits for readability. For large HTML templates, use string builders or load from embedded resources:

var templateHtml = File.ReadAllText("template.html");
var populatedHtml = templateHtml
    .Replace("{{CustomerName}}", customer.Name)
    .Replace("{{InvoiceTotal}}", invoice.Total.ToString("C"));

var pdf = renderer.RenderHtmlAsPdf(populatedHtml);
Enter fullscreen mode Exit fullscreen mode

Or use StringBuilder for complex assembly:

var sb = new StringBuilder();
sb.Append("<!DOCTYPE html><html><body>");
sb.Append($"<h1>{reportTitle}</h1>");

foreach (var item in items)
{
    sb.Append($"<p>{item.Description}: {item.Amount:C}</p>");
}

sb.Append("</body></html>");

var pdf = renderer.RenderHtmlAsPdf(sb.ToString());
Enter fullscreen mode Exit fullscreen mode

For production systems, I use template engines (Scriban, Liquid, Handlebars) or Razor views rather than string concatenation. Template engines provide proper escaping, prevent injection attacks, and enable non-developers to edit templates.

What Common Issues Should I Watch For?

Missing fonts: If HTML references fonts not installed on the server, text renders with fallback fonts. Either install fonts server-side or embed fonts using @font-face with Data URIs or hosted URLs.

Security restrictions: By default, IronPDF prevents loading external resources from different origins (CORS). Disable for trusted content:

renderer.RenderingOptions.EnableWebSecurity = false;
Enter fullscreen mode Exit fullscreen mode

Only disable for HTML you control — enabling for user-generated HTML creates security risks.

Encoding issues: Ensure HTML strings use UTF-8 encoding and include charset declaration:

<meta charset="UTF-8">
Enter fullscreen mode Exit fullscreen mode

Without this, special characters (accents, symbols, non-Latin scripts) may render incorrectly.

Performance with large HTML: Rendering 100+ page PDFs from HTML strings takes time. For batch generation, process in parallel or consider pre-rendering common sections.

I've debugged systems where PDFs rendered blank because external CSS failed to load due to CORS restrictions. Adding EnableWebSecurity = false for internal template URLs resolved the issue.

Quick Reference

Scenario Code Use Case
Basic HTML string renderer.RenderHtmlAsPdf(html) Simple conversion
External resources renderer.RenderingOptions.BaseUrlOrPath = url CSS, images, fonts
Self-contained HTML Use inline CSS + Data URIs No external dependencies
JavaScript content renderer.RenderingOptions.WaitFor.RenderDelay(ms) Charts, dynamic content
Razor views Render view to string, then PDF ASP.NET templating
Template variables String.Replace() or template engine Dynamic content
Disable security EnableWebSecurity = false Trusted internal content

Key Principles:

  • IronPDF uses Chromium — if HTML works in Chrome, PDF renders identically
  • Set BaseUrlOrPath when HTML references external resources (CSS, images)
  • Use <meta charset="UTF-8"> for international characters
  • WaitFor.RenderDelay() or WaitFor.NetworkIdle() for JavaScript-heavy content
  • Inline CSS and Data URI images create self-contained HTML (no external dependencies)
  • IronPDF far simpler than iTextSharp's XML approach
  • wkhtmltopdf is deprecated with outdated WebKit — avoid for new projects
  • Test HTML in Chrome browser first — IronPDF will match that rendering

The complete HTML to PDF guide includes advanced examples for authentication, custom headers, and viewport configuration.


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)