DEV Community

IronSoftware
IronSoftware

Posted on

Migrating from wkhtmltopdf to IronPDF (Developer Guide)

Developers searching for a wkhtmltopdf alternative in 2024 and beyond face an unavoidable reality: the project is dead. The GitHub repository was archived in January 2023, the underlying Qt WebKit engine stopped receiving updates in 2012, and critical security vulnerabilities like CVE-2022-35583 will never be patched. For .NET developers using DinkToPdf, Rotativa, TuesPechkin, or NReco.PdfGenerator, migration is not optional—it is a technical necessity.

This guide provides a complete migration path from wkhtmltopdf and its .NET wrappers to IronPDF. It covers removing native dependencies, mapping command-line options to IronPDF settings, converting headers and footers, and simplifying Docker deployments.

Why Migrate from wkhtmltopdf

The decision to replace wkhtmltopdf is driven by five categories of problems that cannot be fixed because the project is no longer maintained.

Security Vulnerabilities

CVE-2022-35583 documents a Server-Side Request Forgery vulnerability with a CVSS score of 9.8 (Critical). Attackers can inject iframe tags that cause wkhtmltopdf to make requests to internal network resources, potentially exposing cloud metadata endpoints, internal APIs, and sensitive data. The wkhtmltopdf maintainers disputed whether this was a library-level vulnerability, but the practical effect is that any application processing untrusted HTML through wkhtmltopdf is at risk.

Additional vulnerabilities exist:

  • CVE-2020-21365: Directory traversal allowing local file disclosure
  • Undisclosed local file inclusion vectors documented by security researchers

Since the project is archived, these vulnerabilities will never receive patches.

Deprecated Rendering Engine

wkhtmltopdf uses Qt WebKit, a rendering engine that:

  • Stopped receiving updates in 2012
  • Was deprecated by Qt in 2015
  • Was removed from Qt in 2016

The engine predates CSS Flexbox (standardized 2012, widespread 2015+), CSS Grid (standardized 2017), CSS Custom Properties, and ES6 JavaScript. Modern web frameworks like Bootstrap 5, Tailwind CSS, React, Vue, and Angular produce HTML that wkhtmltopdf cannot render correctly.

No ARM64 Support

wkhtmltopdf does not support ARM64 architecture. This affects:

  • Apple Silicon Macs (M1, M2, M3, M4 processors)
  • AWS Graviton instances
  • Azure ARM-based VMs
  • Raspberry Pi deployments
  • ARM-based Kubernetes nodes

Developers on Apple Silicon currently run wkhtmltopdf through Rosetta 2 emulation, which impacts performance and adds complexity. There will never be a native ARM64 build.

Native Dependency Complexity

wkhtmltopdf requires platform-specific native libraries:

  • Windows: libwkhtmltox.dll and Visual C++ 2013 Redistributable
  • Linux: libwkhtmltox.so plus X11 libraries, fontconfig, and often xvfb
  • macOS: libwkhtmltox.dylib with specific framework dependencies

The .NET wrappers (DinkToPdf, TuesPechkin, Rotativa) add another layer of complexity with P/Invoke configuration, architecture detection (32-bit vs 64-bit), and platform-specific deployment logic.

Archived Project Status

The wkhtmltopdf GitHub repository was archived on January 2, 2023. The wkhtmltopdf organization itself was marked as archived on July 10, 2024. This means:

  • No bug fixes
  • No security patches
  • No new features
  • No response to issues
  • No pull request reviews

The dependent .NET wrappers face similar abandonment:

  • DinkToPdf: Last meaningful commit in 2018
  • TuesPechkin: Last update in 2015
  • Rotativa: Not updated for modern .NET Core/5/6/7/8

Step 1: Remove wkhtmltopdf Dependencies

The first step in migration is removing the wkhtmltopdf-related packages and files from your project.

Remove NuGet Packages

<!-- Remove these packages from your .csproj -->
<PackageReference Include="DinkToPdf" Version="*" Remove="DinkToPdf" />
<PackageReference Include="TuesPechkin" Version="*" Remove="TuesPechkin" />
<PackageReference Include="TuesPechkin.Wkhtmltox.Win32" Version="*" Remove="TuesPechkin.Wkhtmltox.Win32" />
<PackageReference Include="TuesPechkin.Wkhtmltox.Win64" Version="*" Remove="TuesPechkin.Wkhtmltox.Win64" />
<PackageReference Include="Rotativa.AspNetCore" Version="*" Remove="Rotativa.AspNetCore" />
<PackageReference Include="NReco.PdfGenerator" Version="*" Remove="NReco.PdfGenerator" />
Enter fullscreen mode Exit fullscreen mode

Or via command line:

dotnet remove package DinkToPdf
dotnet remove package TuesPechkin
dotnet remove package Rotativa.AspNetCore
dotnet remove package NReco.PdfGenerator
Enter fullscreen mode Exit fullscreen mode

Add IronPDF

dotnet add package IronPdf
Enter fullscreen mode Exit fullscreen mode

For ASP.NET Core MVC projects that need Razor view rendering:

dotnet add package IronPdf.Extensions.Mvc.Core
Enter fullscreen mode Exit fullscreen mode

Remove Native Library Files

Delete any wkhtmltopdf binaries from your project:

# Files to remove
libwkhtmltox.dll
libwkhtmltox.so
libwkhtmltox.dylib
wkhtmltopdf.exe
wkhtmltopdf (Linux binary)
Enter fullscreen mode Exit fullscreen mode

Remove project file entries that copy these libraries:

<!-- Remove these entries from .csproj -->
<ItemGroup>
  <None Update="libwkhtmltox.dll" Remove="libwkhtmltox.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
  <None Update="libwkhtmltox.so" Remove="libwkhtmltox.so">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>
Enter fullscreen mode Exit fullscreen mode

Remove Rotativa Configuration

If using Rotativa, remove the setup code from Program.cs or Startup.cs:

// Remove this line
// RotativaConfiguration.Setup(env.WebRootPath, "Rotativa");
Enter fullscreen mode Exit fullscreen mode

Step 2: API Mapping - Command Line Options to IronPDF

wkhtmltopdf is configured through command-line arguments. The .NET wrappers expose these as properties on settings objects. The following table maps common wkhtmltopdf options to their IronPDF equivalents.

Page Size and Orientation

wkhtmltopdf Option DinkToPdf Property IronPDF Equivalent
--page-size A4 GlobalSettings.PaperSize = PaperKind.A4 RenderingOptions.PaperSize = PdfPaperSize.A4
--page-size Letter GlobalSettings.PaperSize = PaperKind.Letter RenderingOptions.PaperSize = PdfPaperSize.Letter
--orientation Landscape GlobalSettings.Orientation = Orientation.Landscape RenderingOptions.PaperOrientation = PdfPaperOrientation.Landscape
--page-width 210mm GlobalSettings.PageWidth = "210mm" RenderingOptions.SetCustomPaperSizeinMilimeters(210, 297)
--page-height 297mm GlobalSettings.PageHeight = "297mm" RenderingOptions.SetCustomPaperSizeinMilimeters(210, 297)

Margins

wkhtmltopdf Option DinkToPdf Property IronPDF Equivalent
--margin-top 10mm GlobalSettings.Margins.Top = 10 RenderingOptions.MarginTop = 10
--margin-bottom 10mm GlobalSettings.Margins.Bottom = 10 RenderingOptions.MarginBottom = 10
--margin-left 10mm GlobalSettings.Margins.Left = 10 RenderingOptions.MarginLeft = 10
--margin-right 10mm GlobalSettings.Margins.Right = 10 RenderingOptions.MarginRight = 10

JavaScript and Rendering

wkhtmltopdf Option DinkToPdf Property IronPDF Equivalent
--javascript-delay 500 ObjectSettings.LoadSettings.JSDelay = 500 RenderingOptions.WaitFor.RenderDelay(500)
--enable-javascript Default enabled RenderingOptions.EnableJavaScript = true
--disable-javascript ObjectSettings.LoadSettings.DisableJavaScript = true RenderingOptions.EnableJavaScript = false
--no-background ObjectSettings.WebSettings.Background = false RenderingOptions.PrintHtmlBackgrounds = false
--background ObjectSettings.WebSettings.Background = true RenderingOptions.PrintHtmlBackgrounds = true
--print-media-type ObjectSettings.WebSettings.PrintMediaType = true RenderingOptions.CssMediaType = PdfCssMediaType.Print
--no-stop-slow-scripts N/A RenderingOptions.Timeout = 120

Output Control

wkhtmltopdf Option DinkToPdf Property IronPDF Equivalent
--grayscale GlobalSettings.ColorMode = ColorMode.Grayscale RenderingOptions.GrayScale = true
--title "Doc Title" GlobalSettings.DocumentTitle = "Doc Title" RenderingOptions.Title = "Doc Title"
--dpi 300 GlobalSettings.DPI = 300 N/A - Chromium handles DPI automatically
--zoom 1.5 GlobalSettings.Zoom = 1.5 RenderingOptions.Zoom = 150 (percentage)

Code Example: Full Migration

Before (DinkToPdf):

using DinkToPdf;
using DinkToPdf.Contracts;

public class PdfService
{
    private readonly IConverter _converter;

    public PdfService(IConverter converter)
    {
        _converter = converter;
    }

    public byte[] GeneratePdf(string htmlContent)
    {
        var doc = new HtmlToPdfDocument()
        {
            GlobalSettings = {
                ColorMode = ColorMode.Color,
                Orientation = Orientation.Portrait,
                PaperSize = PaperKind.A4,
                Margins = new MarginSettings {
                    Top = 10,
                    Bottom = 10,
                    Left = 10,
                    Right = 10
                },
                DocumentTitle = "Generated Report"
            },
            Objects = {
                new ObjectSettings {
                    PagesCount = true,
                    HtmlContent = htmlContent,
                    WebSettings = {
                        DefaultEncoding = "utf-8",
                        PrintMediaType = true,
                        Background = true
                    },
                    LoadSettings = {
                        JSDelay = 500
                    }
                }
            }
        };

        return _converter.Convert(doc);
    }
}
Enter fullscreen mode Exit fullscreen mode

After (IronPDF):

using IronPdf;

public class PdfService
{
    public byte[] GeneratePdf(string htmlContent)
    {
        // No converter injection needed - IronPDF is stateless
        var renderer = new ChromePdfRenderer();

        // Page configuration
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
        renderer.RenderingOptions.PaperOrientation = IronPdf.Rendering.PdfPaperOrientation.Portrait;

        // Margins in millimeters
        renderer.RenderingOptions.MarginTop = 10;
        renderer.RenderingOptions.MarginBottom = 10;
        renderer.RenderingOptions.MarginLeft = 10;
        renderer.RenderingOptions.MarginRight = 10;

        // Document metadata
        renderer.RenderingOptions.Title = "Generated Report";

        // Rendering options
        renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
        renderer.RenderingOptions.PrintHtmlBackgrounds = true;
        renderer.RenderingOptions.EnableJavaScript = true;

        // JavaScript delay - wait for dynamic content
        renderer.RenderingOptions.WaitFor.RenderDelay(500);

        // Render and return
        using var pdf = renderer.RenderHtmlAsPdf(htmlContent);
        return pdf.BinaryData;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Headers and Footers Migration

wkhtmltopdf supports headers and footers through separate HTML files or inline HTML. IronPDF provides equivalent functionality with additional features like page numbers and date stamping.

wkhtmltopdf Header/Footer Approach

wkhtmltopdf headers and footers are configured with command-line options:

wkhtmltopdf --header-html header.html --footer-html footer.html input.html output.pdf
Enter fullscreen mode Exit fullscreen mode

Or with inline text:

wkhtmltopdf --header-center "Page [page] of [toPage]" --footer-left "[date]" input.html output.pdf
Enter fullscreen mode Exit fullscreen mode

DinkToPdf Header/Footer Code

// DinkToPdf approach
var doc = new HtmlToPdfDocument()
{
    Objects = {
        new ObjectSettings {
            HtmlContent = mainContent,
            HeaderSettings = {
                FontSize = 9,
                Right = "Page [page] of [toPage]",
                Line = true,
                Spacing = 2.812
            },
            FooterSettings = {
                FontSize = 9,
                Left = "[date]",
                Center = "Confidential",
                Right = "[page]"
            }
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

IronPDF Header/Footer Migration

IronPDF headers and footers support full HTML with CSS styling:

using IronPdf;

public class HeaderFooterMigration
{
    public byte[] GenerateWithHeadersFooters(string mainContent)
    {
        var renderer = new ChromePdfRenderer();

        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;

        // HTML Header with styling
        renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter()
        {
            HtmlFragment = @"
                <div style='width: 100%; font-family: Arial, sans-serif; font-size: 10px;'>
                    <div style='float: left;'>
                        <img src='https://example.com/logo.png' style='height: 30px;' />
                    </div>
                    <div style='float: right; text-align: right;'>
                        Page {page} of {total-pages}
                    </div>
                    <div style='clear: both; border-bottom: 1px solid #ccc;'></div>
                </div>",
            DrawDividerLine = false,
            MaxHeight = 25 // millimeters
        };

        // HTML Footer with date and page number
        renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter()
        {
            HtmlFragment = @"
                <div style='width: 100%; font-family: Arial, sans-serif; font-size: 9px; color: #666;'>
                    <div style='border-top: 1px solid #ccc; padding-top: 5px;'>
                        <span style='float: left;'>{date} {time}</span>
                        <span style='float: right;'>Page {page} of {total-pages}</span>
                    </div>
                </div>",
            DrawDividerLine = false,
            MaxHeight = 20
        };

        using var pdf = renderer.RenderHtmlAsPdf(mainContent);
        return pdf.BinaryData;
    }
}
Enter fullscreen mode Exit fullscreen mode

Header/Footer Placeholder Mapping

wkhtmltopdf Placeholder IronPDF Placeholder
[page] {page}
[toPage] {total-pages}
[date] {date}
[time] {time}
[title] {html-title}
[webpage] {url}

Simple Text Headers/Footers

For simple text-based headers and footers without HTML complexity:

using IronPdf;

public class SimpleHeaderFooter
{
    public byte[] GenerateWithTextHeaders(string mainContent)
    {
        var renderer = new ChromePdfRenderer();

        // Simple text header
        renderer.RenderingOptions.TextHeader = new TextHeaderFooter()
        {
            CenterText = "Company Report",
            RightText = "{page} of {total-pages}",
            FontFamily = "Arial",
            FontSize = 10,
            DrawDividerLine = true
        };

        // Simple text footer
        renderer.RenderingOptions.TextFooter = new TextHeaderFooter()
        {
            LeftText = "{date}",
            CenterText = "Confidential",
            RightText = "Page {page}",
            FontFamily = "Arial",
            FontSize = 9,
            DrawDividerLine = true
        };

        using var pdf = renderer.RenderHtmlAsPdf(mainContent);
        return pdf.BinaryData;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: CSS and JavaScript Now Work Correctly

One of the primary reasons to migrate is that modern CSS and JavaScript fail silently in wkhtmltopdf. After migration, these features work as expected.

CSS Features That Now Work

using IronPdf;

public class ModernCssExample
{
    public void RenderModernCss()
    {
        var renderer = new ChromePdfRenderer();

        string html = @"
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                /* CSS Flexbox - ignored by wkhtmltopdf, works in IronPDF */
                .flex-container {
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    gap: 20px;
                }

                /* CSS Grid - ignored by wkhtmltopdf, works in IronPDF */
                .grid-layout {
                    display: grid;
                    grid-template-columns: repeat(3, 1fr);
                    grid-gap: 15px;
                }

                /* CSS Variables - ignored by wkhtmltopdf, works in IronPDF */
                :root {
                    --primary-color: #2563eb;
                    --spacing: 1rem;
                }

                .themed-element {
                    color: var(--primary-color);
                    padding: var(--spacing);
                }

                /* CSS calc() - buggy in wkhtmltopdf, works in IronPDF */
                .calculated-width {
                    width: calc(100% - 60px);
                    margin: 0 30px;
                }

                /* CSS Filters - ignored by wkhtmltopdf, works in IronPDF */
                .filtered-image {
                    filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.3));
                }

                /* Modern gradients work correctly */
                .gradient-background {
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                }
            </style>
        </head>
        <body>
            <div class='flex-container'>
                <div>Flex Item 1</div>
                <div>Flex Item 2</div>
                <div>Flex Item 3</div>
            </div>

            <div class='grid-layout'>
                <div>Grid Cell 1</div>
                <div>Grid Cell 2</div>
                <div>Grid Cell 3</div>
            </div>

            <div class='themed-element'>This uses CSS variables</div>
        </body>
        </html>";

        using var pdf = renderer.RenderHtmlAsPdf(html);
        pdf.SaveAs("modern-css.pdf");
    }
}
Enter fullscreen mode Exit fullscreen mode

JavaScript Frameworks Now Render

React, Vue, Angular, and other JavaScript frameworks that generate DOM content at runtime now render correctly:

using IronPdf;

public class JavaScriptRenderingExample
{
    public void RenderDynamicContent()
    {
        var renderer = new ChromePdfRenderer();

        // Enable JavaScript execution
        renderer.RenderingOptions.EnableJavaScript = true;

        // Wait for JavaScript to complete rendering
        renderer.RenderingOptions.WaitFor.JavaScript(2000);

        string html = @"
        <!DOCTYPE html>
        <html>
        <head>
            <script src='https://unpkg.com/vue@3/dist/vue.global.js'></script>
        </head>
        <body>
            <div id='app'>
                <h1>{{ message }}</h1>
                <ul>
                    <li v-for='item in items' :key='item.id'>{{ item.name }}</li>
                </ul>
            </div>

            <script>
                const { createApp, ref } = Vue;

                createApp({
                    setup() {
                        const message = ref('Dynamic Content from Vue');
                        const items = ref([
                            { id: 1, name: 'Item One' },
                            { id: 2, name: 'Item Two' },
                            { id: 3, name: 'Item Three' }
                        ]);
                        return { message, items };
                    }
                }).mount('#app');
            </script>
        </body>
        </html>";

        using var pdf = renderer.RenderHtmlAsPdf(html);
        pdf.SaveAs("vue-rendered.pdf");
    }
}
Enter fullscreen mode Exit fullscreen mode

Web Fonts Load Correctly

wkhtmltopdf has inconsistent web font support. IronPDF loads Google Fonts and other web fonts reliably:

using IronPdf;

public class WebFontExample
{
    public void RenderWithWebFonts()
    {
        var renderer = new ChromePdfRenderer();
        renderer.RenderingOptions.EnableJavaScript = true;

        string html = @"
        <!DOCTYPE html>
        <html>
        <head>
            <link href='https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Playfair+Display&display=swap' rel='stylesheet'>
            <style>
                body { font-family: 'Roboto', sans-serif; }
                h1 { font-family: 'Playfair Display', serif; }
                .bold { font-weight: 700; }
            </style>
        </head>
        <body>
            <h1>Elegant Heading in Playfair Display</h1>
            <p>Body text in Roboto font.</p>
            <p class='bold'>Bold text renders correctly.</p>
        </body>
        </html>";

        using var pdf = renderer.RenderHtmlAsPdf(html);
        pdf.SaveAs("web-fonts.pdf");
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Docker Simplification

wkhtmltopdf in Docker requires xvfb (X Virtual Framebuffer), X11 libraries, font packages, and often sandbox workarounds. IronPDF eliminates these requirements.

Before: wkhtmltopdf Dockerfile

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

# Install wkhtmltopdf and ALL its dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
    # wkhtmltopdf binary
    wget \
    # X11 dependencies
    xvfb \
    libx11-6 \
    libxext6 \
    libxrender1 \
    libxcb1 \
    # Font dependencies
    fontconfig \
    libfreetype6 \
    fonts-dejavu-core \
    fonts-liberation \
    xfonts-75dpi \
    xfonts-base \
    # Image libraries
    libjpeg62-turbo \
    libpng16-16 \
    # Other dependencies
    zlib1g \
    libgdiplus \
    && rm -rf /var/lib/apt/lists/*

# Download and install wkhtmltopdf
RUN wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.bookworm_amd64.deb \
    && dpkg -i wkhtmltox_0.12.6.1-3.bookworm_amd64.deb || apt-get install -f -y \
    && rm wkhtmltox_0.12.6.1-3.bookworm_amd64.deb

# Copy native library for DinkToPdf
COPY libwkhtmltox.so /app/

# Create wrapper script with xvfb
RUN echo '#!/bin/bash\nxvfb-run -a --server-args="-screen 0 1024x768x24" wkhtmltopdf "$@"' > /usr/local/bin/wkhtmltopdf-wrapper \
    && chmod +x /usr/local/bin/wkhtmltopdf-wrapper

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

After: IronPDF Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim AS base

# IronPDF minimal dependencies - NO xvfb, NO X11, NO fonts packages
RUN apt-get update && apt-get install -y --no-install-recommends \
    libc6 \
    libgcc-s1 \
    libstdc++6 \
    && rm -rf /var/lib/apt/lists/*

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

Comparison

Requirement wkhtmltopdf IronPDF
xvfb Required Not needed
X11 libraries Required Not needed
fontconfig Required Not needed
Font packages Required Not needed
Wrapper scripts Required Not needed
Native library files Required Not needed
Added image size ~150MB+ ~30MB
Build complexity High Low

ARM64/Apple Silicon Docker Support

IronPDF supports ARM64 architecture natively, enabling deployment on Apple Silicon development machines and ARM-based cloud instances:

# Works on both AMD64 and ARM64
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim

# Same minimal dependencies work on ARM64
RUN apt-get update && apt-get install -y --no-install-recommends \
    libc6 \
    libgcc-s1 \
    libstdc++6 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "MyApp.dll"]
Enter fullscreen mode Exit fullscreen mode

Step 6: Rotativa Migration Pattern

Rotativa provided convenient ViewAsPdf and ActionAsPdf action results for ASP.NET MVC. Migrating to IronPDF requires rendering Razor views to HTML strings first.

Before: Rotativa

using Rotativa.AspNetCore;
using Microsoft.AspNetCore.Mvc;

public class InvoiceController : Controller
{
    public IActionResult PrintInvoice(int id)
    {
        var model = GetInvoice(id);
        return new ViewAsPdf("Invoice", model)
        {
            FileName = $"Invoice_{id}.pdf",
            PageSize = Rotativa.Options.Size.A4,
            PageMargins = new Rotativa.Options.Margins(10, 10, 10, 10)
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

After: IronPDF with Razor View Rendering

using IronPdf;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;

public class InvoiceController : Controller
{
    private readonly ICompositeViewEngine _viewEngine;
    private readonly ITempDataProvider _tempDataProvider;

    public InvoiceController(
        ICompositeViewEngine viewEngine,
        ITempDataProvider tempDataProvider)
    {
        _viewEngine = viewEngine;
        _tempDataProvider = tempDataProvider;
    }

    public async Task<IActionResult> PrintInvoice(int id)
    {
        var model = GetInvoice(id);

        // Render Razor view to HTML string
        var htmlContent = await RenderViewToStringAsync("Invoice", model);

        // Convert to PDF
        var renderer = new ChromePdfRenderer();
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
        renderer.RenderingOptions.MarginTop = 10;
        renderer.RenderingOptions.MarginBottom = 10;
        renderer.RenderingOptions.MarginLeft = 10;
        renderer.RenderingOptions.MarginRight = 10;

        using var pdf = renderer.RenderHtmlAsPdf(htmlContent, Request.Scheme + "://" + Request.Host);

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

    private async Task<string> RenderViewToStringAsync(string viewName, object model)
    {
        ViewData.Model = model;

        using var writer = new StringWriter();
        var viewResult = _viewEngine.FindView(ControllerContext, viewName, false);

        if (!viewResult.Success)
        {
            throw new InvalidOperationException($"View '{viewName}' not found.");
        }

        var viewContext = new ViewContext(
            ControllerContext,
            viewResult.View,
            ViewData,
            TempData,
            writer,
            new HtmlHelperOptions()
        );

        await viewResult.View.RenderAsync(viewContext);
        return writer.GetStringBuilder().ToString();
    }
}
Enter fullscreen mode Exit fullscreen mode

ActionAsPdf Equivalent

For scenarios where you need to capture content from a URL (similar to Rotativa's ActionAsPdf):

using IronPdf;
using Microsoft.AspNetCore.Mvc;

public class ReportController : Controller
{
    public IActionResult PrintReport(int id)
    {
        var renderer = new ChromePdfRenderer();

        // Enable JavaScript and wait for dynamic content
        renderer.RenderingOptions.EnableJavaScript = true;
        renderer.RenderingOptions.WaitFor.JavaScript(2000);

        // Render from URL - captures the full page
        var url = Url.Action("ViewReport", "Report", new { id }, Request.Scheme);
        using var pdf = renderer.RenderUrlAsPdf(url);

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

    public IActionResult ViewReport(int id)
    {
        var model = GetReport(id);
        return View(model);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 7: TuesPechkin Migration

TuesPechkin is the oldest .NET wrapper and does not work in .NET Core. Migration is mandatory for any modernization effort.

Before: TuesPechkin

using TuesPechkin;

public class PdfGenerator
{
    private readonly IConverter _converter;

    public PdfGenerator()
    {
        var deployment = new Win64EmbeddedDeployment(
            new TempFolderDeployment()
        );

        _converter = new ThreadSafeConverter(
            new RemotingToolset<PdfToolset>(deployment)
        );
    }

    public byte[] GeneratePdf(string html)
    {
        var document = new HtmlToPdfDocument
        {
            GlobalSettings = {
                ProduceOutline = true,
                DocumentTitle = "Report",
                PaperSize = PaperKind.A4,
                Margins = {
                    Top = 1.0,
                    Bottom = 1.0,
                    Left = 1.0,
                    Right = 1.0,
                    Unit = Unit.Centimeters
                }
            },
            Objects = {
                new ObjectSettings {
                    HtmlText = html
                }
            }
        };

        return _converter.Convert(document);
    }
}
Enter fullscreen mode Exit fullscreen mode

After: IronPDF

using IronPdf;

public class PdfGenerator
{
    // No constructor setup required - IronPDF is stateless

    public byte[] GeneratePdf(string html)
    {
        var renderer = new ChromePdfRenderer();

        // Document settings
        renderer.RenderingOptions.Title = "Report";
        renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;

        // Margins in millimeters (TuesPechkin used centimeters)
        renderer.RenderingOptions.MarginTop = 10;    // 1cm = 10mm
        renderer.RenderingOptions.MarginBottom = 10;
        renderer.RenderingOptions.MarginLeft = 10;
        renderer.RenderingOptions.MarginRight = 10;

        // Create table of contents/outline if needed
        renderer.RenderingOptions.CreatePdfFormsFromHtml = false;

        using var pdf = renderer.RenderHtmlAsPdf(html);
        return pdf.BinaryData;
    }
}
Enter fullscreen mode Exit fullscreen mode

Migration Considerations

Licensing

IronPDF is commercial software with per-developer licensing. A free trial is available for evaluation. For teams accustomed to wkhtmltopdf's open-source model, this represents a licensing cost. However, the total cost of ownership should factor in:

  • Engineering time saved not debugging native library issues
  • Security risk of running unpatched software
  • Maintenance burden of legacy workarounds
  • Development velocity with modern CSS/JavaScript support

Rendering Differences

IronPDF uses Chromium instead of Qt WebKit. While this is an improvement for modern web standards, some documents may render slightly differently:

  • Font metrics may vary slightly
  • Page break calculations may differ
  • Legacy CSS hacks for WebKit may be unnecessary (and should be removed)

Test your output thoroughly during migration, particularly for documents with precise layout requirements like invoices and legal forms.

Package Size

IronPDF includes an embedded Chromium engine, resulting in a larger NuGet package (~200MB) compared to wkhtmltopdf wrappers. This affects:

  • Initial package download time
  • Build artifact size
  • Container image size (though this is often offset by removing X11 dependencies)

For serverless environments with strict package size limits, check deployment constraints.

Memory Usage

Chromium-based rendering uses more memory than Qt WebKit for simple documents. For high-volume processing:

// Dispose PDFs explicitly to release memory
using (var pdf = renderer.RenderHtmlAsPdf(html))
{
    var bytes = pdf.BinaryData;
    // Process bytes
}
// PDF resources released here

// Or use explicit garbage collection for high-volume scenarios
if (documentsProcessed % 100 == 0)
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
}
Enter fullscreen mode Exit fullscreen mode

API Reference

For complete documentation on the methods and classes used in this migration guide:

Conclusion

Migrating from wkhtmltopdf to IronPDF addresses fundamental problems that cannot be resolved by staying with the deprecated toolchain. The archived project status, unpatched security vulnerabilities, outdated rendering engine, and missing ARM64 support make migration necessary rather than optional.

The migration process involves removing native dependencies, mapping configuration options, updating header/footer implementations, and simplifying Docker deployments. The result is a maintainable codebase with modern CSS/JavaScript support, active security updates, and cross-platform compatibility including ARM64.


Jacob Mellor has been developing commercial software for over 25 years and is CTO at Iron Software, where he originally built IronPDF.


References

  1. wkhtmltopdf GitHub Repository (Archived){:rel="nofollow"} - Official repository showing archived status
  2. CVE-2022-35583 - SSRF Vulnerability{:rel="nofollow"} - Critical security vulnerability details
  3. DinkToPdf GitHub Repository{:rel="nofollow"} - .NET Core wrapper (last update 2018)
  4. Rotativa GitHub Repository{:rel="nofollow"} - ASP.NET MVC wrapper
  5. TuesPechkin GitHub Repository{:rel="nofollow"} - .NET Framework wrapper
  6. Qt WebKit Deprecation{:rel="nofollow"} - Qt documentation on WebKit deprecation
  7. wkhtmltopdf Known Issues{:rel="nofollow"} - Official status page
  8. CVE-2020-21365 - Directory Traversal{:rel="nofollow"} - File disclosure vulnerability

For comprehensive IronPDF documentation, tutorials, and code examples, visit ironpdf.com.

Top comments (0)