How to Migrate from Rotativa to IronPDF in C#
Migrating from Rotativa to IronPDF addresses critical security vulnerabilities while modernizing your PDF generation workflow. This guide provides a complete, step-by-step migration path that eliminates the abandoned wkhtmltopdf dependency, enables support for modern CSS and JavaScript, and provides cross-platform compatibility beyond ASP.NET MVC.
Why Migrate from Rotativa to IronPDF
Understanding Rotativa
Rotativa has long been a popular choice among developers for generating PDFs in C#. It leverages the wkhtmltopdf tool to convert HTML content into PDF format. Rotativa is an open-source library specifically designed for ASP.NET MVC applications. However, while it has attracted a significant audience, Rotativa's reliance on an outdated technology stack presents challenges that might not be immediately evident to every developer.
At its core, Rotativa provides a simple way to integrate PDF generation into ASP.NET MVC projects, taking advantage of wkhtmltopdf for its backend functionalities.
Critical Security Advisory
Rotativa wraps wkhtmltopdf, which has CRITICAL UNPATCHED SECURITY VULNERABILITIES.
| Attribute | Value |
|---|---|
| CVE ID | CVE-2022-35583 |
| Severity | CRITICAL (9.8/10) |
| Attack Vector | Network |
| Status | WILL NEVER BE PATCHED |
| Affected | ALL Rotativa versions |
wkhtmltopdf was officially abandoned in December 2022. The maintainers explicitly stated they will NOT fix security vulnerabilities. Every application using Rotativa is permanently exposed.
How the Attack Works
<!-- Attacker submits this content via your MVC model -->
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<img src="http://internal-database:5432/admin" /><!-- Attacker submits this content via your MVC model -->
<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<img src="http://internal-database:5432/admin" />Impact:
- Access AWS/Azure/GCP cloud metadata endpoints
- Steal internal API data and credentials
- Port scan internal networks
- Exfiltrate sensitive configuration
The Technology Crisis
Rotativa wraps wkhtmltopdf, which uses:
- Qt WebKit 4.8 (from 2012)
- No Flexbox support
- No CSS Grid support
- Broken JavaScript execution
- No ES6+ support
Rotativa vs IronPDF Comparison
| Feature | Rotativa | IronPDF |
|---|---|---|
| Project Compatibility | ASP.NET MVC Only | Any .NET Project Type (MVC, Razor Pages, Blazor, etc.) |
| Maintenance | Abandoned | Actively Maintained |
| Security | Vulnerable due to wkhtmltopdf dependencies (CVE-2022-35583) | Regular updates and security patches |
| HTML Rendering | Outdated WebKit | Modern Chromium |
| CSS3 | Partial | Full support |
| Flexbox/Grid | Not supported | Full support |
| JavaScript | Unreliable | Full ES6+ |
| Razor Pages | Not supported | Full support |
| Blazor | Not supported | Full support |
| PDF Manipulation | Not available | Full |
| Digital Signatures | Not available | Full |
| PDF/A Compliance | Not available | Full |
| Async/Await | Synchronous only | Full async |
| Open Source | Yes, MIT License | No, Commercial License |
For teams planning .NET 10 and C# 14 adoption through 2025 and 2026, IronPDF provides modern Chromium rendering and cross-platform support that Rotativa cannot offer.
Before You Start
Prerequisites
- .NET Environment: .NET Framework 4.6.2+ or .NET Core 3.1+ / .NET 5/6/7/8/9+
- NuGet Access: Ability to install NuGet packages
- IronPDF License: Obtain your license key from ironpdf.com
NuGet Package Changes
# Remove Rotativa
dotnet remove package Rotativa
dotnet remove package Rotativa.AspNetCore
# Install IronPDF
dotnet add package IronPdf# Remove Rotativa
dotnet remove package Rotativa
dotnet remove package Rotativa.AspNetCore
# Install IronPDF
dotnet add package IronPdfRemove wkhtmltopdf Binaries
Delete these files from your project:
wkhtmltopdf.exewkhtmltox.dll- Any
Rotativa/folders
These are the source of CVE-2022-35583. IronPDF needs no native binaries.
License Configuration
// Add in Program.cs or Startup.cs
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";// Add in Program.cs or Startup.cs
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";Complete API Reference
Namespace Changes
// Before: Rotativa
using Rotativa;
using Rotativa.Options;
using Rotativa.AspNetCore;
// After: IronPDF
using IronPdf;
using IronPdf.Rendering;// Before: Rotativa
using Rotativa;
using Rotativa.Options;
using Rotativa.AspNetCore;
// After: IronPDF
using IronPdf;
using IronPdf.Rendering;Core Class Mappings
| Rotativa Class | IronPDF Equivalent | Notes |
|---|---|---|
ViewAsPdf | ChromePdfRenderer | Render HTML |
ActionAsPdf | ChromePdfRenderer.RenderUrlAsPdf() | Render URL |
UrlAsPdf | ChromePdfRenderer.RenderUrlAsPdf() | Render URL |
Orientation enum | PdfPaperOrientation enum | Orientation |
Size enum | PdfPaperSize enum | Paper size |
Page Placeholder Conversion
| Rotativa Placeholder | IronPDF Placeholder |
|---|---|
[page] | {page} |
[topage] | {total-pages} |
[date] | {date} |
[time] | {time} |
[title] | {html-title} |
[sitepage] | {url} |
Code Migration Examples
Example 1: HTML to PDF Conversion
Before (Rotativa):
// NuGet: Install-Package Rotativa.Core
using Microsoft.AspNetCore.Mvc;
using Rotativa.AspNetCore;
using System.Threading.Tasks;
namespace RotativaExample
{
public class PdfController : Controller
{
public async Task<IActionResult> GeneratePdf()
{
var htmlContent = "<h1>Hello World</h1><p>This is a PDF document.</p>";
// Rotativa requires returning a ViewAsPdf result from MVC controller
return new ViewAsPdf()
{
ViewName = "PdfView",
PageSize = Rotativa.AspNetCore.Options.Size.A4
};
}
}
}// NuGet: Install-Package Rotativa.Core
using Microsoft.AspNetCore.Mvc;
using Rotativa.AspNetCore;
using System.Threading.Tasks;
namespace RotativaExample
{
public class PdfController : Controller
{
public async Task<IActionResult> GeneratePdf()
{
var htmlContent = "<h1>Hello World</h1><p>This is a PDF document.</p>";
// Rotativa requires returning a ViewAsPdf result from MVC controller
return new ViewAsPdf()
{
ViewName = "PdfView",
PageSize = Rotativa.AspNetCore.Options.Size.A4
};
}
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
namespace IronPdfExample
{
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
var htmlContent = "<h1>Hello World</h1><p>This is a PDF document.</p>";
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
pdf.SaveAs("output.pdf");
Console.WriteLine("PDF generated successfully!");
}
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System;
namespace IronPdfExample
{
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
var htmlContent = "<h1>Hello World</h1><p>This is a PDF document.</p>";
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
pdf.SaveAs("output.pdf");
Console.WriteLine("PDF generated successfully!");
}
}
}This example demonstrates the fundamental architectural difference. Rotativa requires returning a ViewAsPdf result from an MVC controller action, tying you to the ASP.NET MVC framework. The pattern only works within the MVC request pipeline and requires a Razor view to render.
IronPDF works anywhere: console applications, web APIs, Blazor, Razor Pages, or any .NET project type. You call RenderHtmlAsPdf() with an HTML string and save the result. No MVC controller required, no view dependency. See the HTML to PDF documentation for comprehensive examples.
Example 2: URL to PDF Conversion
Before (Rotativa):
// NuGet: Install-Package Rotativa.Core
using Microsoft.AspNetCore.Mvc;
using Rotativa.AspNetCore;
using System.Threading.Tasks;
namespace RotativaExample
{
public class UrlPdfController : Controller
{
public async Task<IActionResult> ConvertUrlToPdf()
{
// Rotativa works within MVC framework and returns ActionResult
return new UrlAsPdf("https://www.example.com")
{
FileName = "webpage.pdf",
PageSize = Rotativa.AspNetCore.Options.Size.A4,
PageOrientation = Rotativa.AspNetCore.Options.Orientation.Portrait
};
}
}
}// NuGet: Install-Package Rotativa.Core
using Microsoft.AspNetCore.Mvc;
using Rotativa.AspNetCore;
using System.Threading.Tasks;
namespace RotativaExample
{
public class UrlPdfController : Controller
{
public async Task<IActionResult> ConvertUrlToPdf()
{
// Rotativa works within MVC framework and returns ActionResult
return new UrlAsPdf("https://www.example.com")
{
FileName = "webpage.pdf",
PageSize = Rotativa.AspNetCore.Options.Size.A4,
PageOrientation = Rotativa.AspNetCore.Options.Orientation.Portrait
};
}
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
namespace IronPdfExample
{
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://www.example.com");
pdf.SaveAs("webpage.pdf");
Console.WriteLine("URL converted to PDF successfully!");
}
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using System;
namespace IronPdfExample
{
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderUrlAsPdf("https://www.example.com");
pdf.SaveAs("webpage.pdf");
Console.WriteLine("URL converted to PDF successfully!");
}
}
}Rotativa's UrlAsPdf class requires returning an ActionResult from an MVC controller. IronPDF's RenderUrlAsPdf() method can be called from any context and returns a PdfDocument object directly. The URL rendering uses modern Chromium instead of wkhtmltopdf's vulnerable and outdated WebKit engine. Learn more in our tutorials.
Example 3: Headers and Footers with Page Numbers
Before (Rotativa):
// NuGet: Install-Package Rotativa.Core
using Microsoft.AspNetCore.Mvc;
using Rotativa.AspNetCore;
using Rotativa.AspNetCore.Options;
using System.Threading.Tasks;
namespace RotativaExample
{
public class HeaderFooterController : Controller
{
public async Task<IActionResult> GeneratePdfWithHeaderFooter()
{
return new ViewAsPdf("Report")
{
PageSize = Size.A4,
PageMargins = new Margins(20, 10, 20, 10),
CustomSwitches = "--header-center \"Page Header\" --footer-center \"Page [page] of [toPage]\""
};
}
}
}// NuGet: Install-Package Rotativa.Core
using Microsoft.AspNetCore.Mvc;
using Rotativa.AspNetCore;
using Rotativa.AspNetCore.Options;
using System.Threading.Tasks;
namespace RotativaExample
{
public class HeaderFooterController : Controller
{
public async Task<IActionResult> GeneratePdfWithHeaderFooter()
{
return new ViewAsPdf("Report")
{
PageSize = Size.A4,
PageMargins = new Margins(20, 10, 20, 10),
CustomSwitches = "--header-center \"Page Header\" --footer-center \"Page [page] of [toPage]\""
};
}
}
}After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Rendering;
using System;
namespace IronPdfExample
{
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TextHeader = new TextHeaderFooter()
{
CenterText = "Page Header",
DrawDividerLine = true
};
renderer.RenderingOptions.TextFooter = new TextHeaderFooter()
{
CenterText = "Page {page} of {total-pages}",
DrawDividerLine = true
};
var htmlContent = "<h1>Report Title</h1><p>Report content goes here.</p>";
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
pdf.SaveAs("report.pdf");
Console.WriteLine("PDF with headers and footers created successfully!");
}
}
}// NuGet: Install-Package IronPdf
using IronPdf;
using IronPdf.Rendering;
using System;
namespace IronPdfExample
{
class Program
{
static void Main(string[] args)
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TextHeader = new TextHeaderFooter()
{
CenterText = "Page Header",
DrawDividerLine = true
};
renderer.RenderingOptions.TextFooter = new TextHeaderFooter()
{
CenterText = "Page {page} of {total-pages}",
DrawDividerLine = true
};
var htmlContent = "<h1>Report Title</h1><p>Report content goes here.</p>";
var pdf = renderer.RenderHtmlAsPdf(htmlContent);
pdf.SaveAs("report.pdf");
Console.WriteLine("PDF with headers and footers created successfully!");
}
}
}Rotativa uses CustomSwitches to pass command-line arguments to wkhtmltopdf, including header and footer configuration with placeholders like [page] and [toPage]. This string-based approach is error-prone and hard to validate at compile time.
IronPDF uses strongly-typed TextHeaderFooter objects with properties like CenterText and DrawDividerLine. The placeholder syntax changes from [page] to {page} and from [toPage] to {total-pages}. Typed properties provide IntelliSense, compile-time checking, and no risk of typos.
The MVC-Only Architecture Problem
Rotativa was designed for ASP.NET MVC 5 and earlier:
// ❌ Rotativa - Only works with classic MVC pattern
public class InvoiceController : Controller
{
public ActionResult InvoicePdf(int id)
{
var model = GetInvoice(id);
return new ViewAsPdf("Invoice", model); // Tied to MVC Views
}
}
// Problems:
// - No Razor Pages support
// - No Blazor support
// - No minimal APIs support
// - No ASP.NET Core native integration// ❌ Rotativa - Only works with classic MVC pattern
public class InvoiceController : Controller
{
public ActionResult InvoicePdf(int id)
{
var model = GetInvoice(id);
return new ViewAsPdf("Invoice", model); // Tied to MVC Views
}
}
// Problems:
// - No Razor Pages support
// - No Blazor support
// - No minimal APIs support
// - No ASP.NET Core native integrationIronPDF separates view rendering from PDF generation, which is actually more flexible—you can render any HTML, not just MVC views.
Async Pattern Migration
Rotativa blocks the thread; IronPDF supports full async/await:
// ❌ Rotativa - Blocks the thread
public ActionResult GeneratePdf()
{
return new ViewAsPdf("Report");
// This blocks the request thread until PDF is complete
// Poor scalability under load
}
// ✅ IronPDF - Full async support
public async Task<IActionResult> GeneratePdf()
{
var renderer = new ChromePdfRenderer();
var pdf = await renderer.RenderHtmlAsPdfAsync(html);
return File(pdf.BinaryData, "application/pdf");
// Non-blocking, better scalability
}// ❌ Rotativa - Blocks the thread
public ActionResult GeneratePdf()
{
return new ViewAsPdf("Report");
// This blocks the request thread until PDF is complete
// Poor scalability under load
}
// ✅ IronPDF - Full async support
public async Task<IActionResult> GeneratePdf()
{
var renderer = new ChromePdfRenderer();
var pdf = await renderer.RenderHtmlAsPdfAsync(html);
return File(pdf.BinaryData, "application/pdf");
// Non-blocking, better scalability
}New Capabilities After Migration
After migrating to IronPDF, you gain capabilities that Rotativa cannot provide:
PDF Merging
var merged = PdfDocument.Merge(pdf1, pdf2, pdf3);
merged.SaveAs("complete.pdf");var merged = PdfDocument.Merge(pdf1, pdf2, pdf3);
merged.SaveAs("complete.pdf");Digital Signatures
var signature = new PdfSignature("certificate.pfx", "password");
pdf.Sign(signature);var signature = new PdfSignature("certificate.pfx", "password");
pdf.Sign(signature);Password Protection
pdf.SecuritySettings.UserPassword = "secret";pdf.SecuritySettings.UserPassword = "secret";Watermarks
pdf.ApplyWatermark("<h1 style='color:red; opacity:0.3;'>DRAFT</h1>");pdf.ApplyWatermark("<h1 style='color:red; opacity:0.3;'>DRAFT</h1>");PDF/A Archival Compliance
pdf.SaveAsPdfA("archive.pdf", PdfAVersions.PdfA3b);pdf.SaveAsPdfA("archive.pdf", PdfAVersions.PdfA3b);Modern CSS Support
// This now works (broke in Rotativa)
var html = @"
<div style='display: flex; justify-content: space-between;'>
<div>Left</div>
<div>Right</div>
</div>
<div style='display: grid; grid-template-columns: 1fr 1fr 1fr;'>
<div>Col 1</div><div>Col 2</div><div>Col 3</div>
</div>";
var pdf = renderer.RenderHtmlAsPdf(html); // Works!// This now works (broke in Rotativa)
var html = @"
<div style='display: flex; justify-content: space-between;'>
<div>Left</div>
<div>Right</div>
</div>
<div style='display: grid; grid-template-columns: 1fr 1fr 1fr;'>
<div>Col 1</div><div>Col 2</div><div>Col 3</div>
</div>";
var pdf = renderer.RenderHtmlAsPdf(html); // Works!Migration Checklist
Pre-Migration
- Identify all Rotativa usage in codebase
- Document CustomSwitches used for conversion to RenderingOptions
- Note header/footer placeholder syntax for conversion (
[page]→{page}) - Obtain IronPDF license key from ironpdf.com
Package Changes
- Remove
RotativaandRotativa.AspNetCoreNuGet packages - Delete wkhtmltopdf binaries (
wkhtmltopdf.exe,wkhtmltox.dll) - Install
IronPdfNuGet package
Code Changes
- Update namespace imports (
using Rotativa;→using IronPdf;) - Replace
ViewAsPdfwithChromePdfRenderer+RenderHtmlAsPdf() - Replace
UrlAsPdfwithRenderUrlAsPdf() - Convert
CustomSwitchestoRenderingOptionsproperties - Update placeholder syntax (
[page]→{page},[topage]→{total-pages}) - Replace
PageMarginswith individualMarginTop/MarginBottom/MarginLeft/MarginRight - Change to async pattern where appropriate
- Add license initialization at application startup
Post-Migration
- Verify all PDF generation works correctly
- Compare PDF output quality (Chromium renders more accurately)
- Verify CSS rendering improvements (Flexbox/Grid now work)
- Test JavaScript execution (now reliable with Chromium)
- Verify security scan passes (no more CVE-2022-35583 flags)
- Update Docker configurations to remove wkhtmltopdf installation






