跳至页脚内容
迁移指南

如何用 C# 从 Rotativa 迁移到 IronPDF

从Rotativa迁移到IronPDF可解决关键的安全漏洞,同时实现 PDF 生成工作流程的现代化。 本指南提供了一个完整的、循序渐进的迁移路径,消除了废弃的 wkhtmltopdf 依赖性,实现了对现代 CSS 和 JavaScript 的支持,并提供了超越 ASP.NET MVC 的跨平台兼容性。

为何从Rotativa迁移到 IronPDF.

了解 Rotativa.

Rotativa 长期以来一直是开发人员在 C# 中生成 PDF 的热门选择。 它利用 wkhtmltopdf 工具将 HTML 内容转换为 PDF 格式。Rotativa是专为 ASP.NET MVC 应用程序设计的开源库。 然而,虽然Rotativa吸引了大量受众,但它对过时技术栈的依赖也带来了挑战,这些挑战可能不是每个开发人员都能立即意识到的。

Rotativa 的核心是提供一种简单的方法,将 PDF 生成集成到 ASP.NET MVC 项目中,并利用 wkhtmltopdf 实现其后台功能。

重要安全咨询

Rotativa封装了wkhtmltopdf,该文件存在严重的未编译安全漏洞。

属性 价值
CVE ID CVE-2022-35583
严重性 关键 (9.8/10)
攻击向量 网络
状态 永不打补丁
受影响 所有Rotativa版本

wkhtmltopdf已于2022年12月正式废弃。维护者明确表示他们不会修复安全漏洞。 每个使用Rotativa的应用程序都会永久暴露。

攻击如何工作


<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<img src="http://internal-database:5432/admin" />

<iframe src="http://169.254.169.254/latest/meta-data/iam/security-credentials/"></iframe>
<img src="http://internal-database:5432/admin" />
HTML

影响:

  • 访问 AWS/Azure/GCP 云元数据端点
  • 窃取内部 API 数据和凭证
  • 端口扫描内部网络
  • 渗出敏感配置

技术危机

Rotativa 对 wkhtmltopdf 进行了包装,后者使用的是.NET:

  • Qt WebKit 4.8(2012 年起)
  • 不支持 Flexbox
  • 不支持 CSS 网格
  • JavaScript 执行中断
  • 不支持 ES6+

Rotativa与IronPDF对比

特征 Rotativa IronPDF
项目兼容性 仅限 ASP.NET MVC 任何 .NET 项目类型(MVC、Razor Pages、Blazor 等)
维护 放弃 积极维护
安全性 由于 wkhtmltopdf 依赖关系而存在漏洞 (CVE-2022-35583) 定期更新和安全补丁
HTML 渲染 过时的 WebKit 现代 Chromium
CSS3代码 部分翻译 全面支持
Flexbox/网格 不支持 全面支持
JavaScript语言 不可靠 完整的 ES6+
Razor页面 不支持 全面支持
Blazor 不支持 全面支持
PDF 操作 不可用 满的
数字签名 不可用 满的
PDF/A合规性 不可用 满的
同步/等待 仅同步 完全异步
开放源代码 是,MIT 许可 否,商业许可

对于计划在 2025 年和 2026 年之前采用 .NET 10 和 C# 14 的团队,IronPDF 可提供Rotativa无法提供的现代 Chromium 渲染和跨平台支持。


开始之前

前提条件

  1. .NET 环境: .NET Framework 4.6.2+ 或 .NET Core 3.1+ / .NET 5/6/7/8/9+
  2. NuGet 访问权限:能够安装 NuGet 包
  3. IronPDF 许可证:请从ironpdf.com获取您的许可证密钥。

NuGet 软件包变更

# 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 IronPdf
SHELL

删除 wkhtmltopdf 二进制文件

从您的项目中删除这些文件:

  • wkhtmltopdf.exe
  • wkhtmltox.dll
  • 任何 Rotativa/ 文件夹

这些就是CVE-2022-35583的源头。IronPdf 不需要本地二进制文件。

许可配置

// 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";
$vbLabelText   $csharpLabel

完整的 API 参考

命名空间变更

// 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;
$vbLabelText   $csharpLabel

核心类映射

Rotativa 类 IronPdf 同等产品 备注
ViewAsPdf ChromePdfRenderer 渲染 HTML
ActionAsPdf ChromePdfRenderer.RenderUrlAsPdf() 渲染 URL
UrlAsPdf ChromePdfRenderer.RenderUrlAsPdf() 渲染 URL
方向</code>枚举|PdfPaperOrientation` 枚举 定位
大小</code>枚举|PdfPaperSize` 枚举 纸张大小

页面占位符转换

Rotativa 占位符 IronPdf 占位符
[页面]|{page}`
[topage] {总页数}
[日期] {日期}
[时间] {时间}
[标题] {html-title}
[网站页面] {url}

代码迁移示例

示例 1:HTML 到 PDF 的转换

之前 (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>";

            //Rotativarequires 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>";

            //Rotativarequires returning a ViewAsPdf result from MVC controller
            return new ViewAsPdf()
            {
                ViewName = "PdfView",
                PageSize = Rotativa.AspNetCore.Options.Size.A4
            };
        }
    }
}
$vbLabelText   $csharpLabel

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!");
        }
    }
}
$vbLabelText   $csharpLabel

本例展示了基本的架构差异。Rotativa要求从 MVC 控制器动作返回ViewAsPdf`结果,从而将您与 ASP.NET MVC Framework 绑定。 该模式仅适用于 MVC 请求管道,并需要 Razor 视图来呈现。

IronPDF 适用于任何地方:控制台应用程序、Web API、Blazor、Razor Pages 或任何 .NET 项目类型。 您使用 HTML 字符串调用 RenderHtmlAsPdf() 并保存结果。 无需 MVC 控制器,不依赖视图。 请参阅 HTML 转 PDF 文档,了解全面的示例。

示例 2:URL 到 PDF 的转换

之前 (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()
        {
            //Rotativaworks 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()
        {
            //Rotativaworks 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
            };
        }
    }
}
$vbLabelText   $csharpLabel

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!");
        }
    }
}
$vbLabelText   $csharpLabel

Rotativa 的UrlAsPdf类需要从 MVC 控制器返回 ActionResult 结果。IronPDF的 RenderUrlAsPdf() 方法可在任何上下文中调用,并直接返回 PdfDocument 对象。 URL 渲染使用的是现代 Chromium 引擎,而不是 wkhtmltopdf 脆弱过时的 WebKit 引擎。请在我们的教程中了解更多信息。

示例 3:带页码的页眉和页脚

之前 (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]\""
            };
        }
    }
}
$vbLabelText   $csharpLabel

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!");
        }
    }
}
$vbLabelText   $csharpLabel

Rotativa 使用 CustomSwitches 将命令行参数传递给 wkhtmltopdf,包括带有占位符(如[页面][toPage] )的页眉和页脚配置。 这种基于字符串的方法容易出错,而且很难在编译时进行验证。

IronPDF 使用强类型的 TextHeaderFooter 对象,该对象具有 CenterTextDrawDividerLine 等属性。 占位符语法从[页面]变为{page},从 [toPage] 变为{总页数}。 类型化属性提供了智能提示、编译时检查功能,而且不会出现错别字。


仅 MVC 架构问题

Rotativa 专为 ASP.NET MVC 5 及更早版本设计:

// ❌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 integration
$vbLabelText   $csharpLabel

IronPDF 将视图渲染与 PDF 生成分离开来,这实际上更加灵活--您可以渲染任何 HTML,而不仅仅是 MVC 视图。


同步模式迁移

Rotativa 阻断了线程; IronPdf 支持完全异步/等待:

// ❌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-完全异步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-完全异步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
}
$vbLabelText   $csharpLabel

迁移后的新功能

迁移到IronPDF后,您将获得Rotativa无法提供的功能:

PDF 合并

var merged = PdfDocument.Merge(pdf1, pdf2, pdf3);
merged.SaveAs("complete.pdf");
var merged = PdfDocument.Merge(pdf1, pdf2, pdf3);
merged.SaveAs("complete.pdf");
$vbLabelText   $csharpLabel

数字签名

var signature = new PdfSignature("certificate.pfx", "password");
pdf.Sign(signature);
var signature = new PdfSignature("certificate.pfx", "password");
pdf.Sign(signature);
$vbLabelText   $csharpLabel

密码保护

pdf.SecuritySettings.UserPassword = "secret";
pdf.SecuritySettings.UserPassword = "secret";
$vbLabelText   $csharpLabel

水印

pdf.ApplyWatermark("<h1 style='color:red; opacity:0.3;'>DRAFT</h1>");
pdf.ApplyWatermark("<h1 style='color:red; opacity:0.3;'>DRAFT</h1>");
$vbLabelText   $csharpLabel

PDF/A 存档合规性

pdf.SaveAsPdfA("archive.pdf", PdfAVersions.PdfA3b);
pdf.SaveAsPdfA("archive.pdf", PdfAVersions.PdfA3b);
$vbLabelText   $csharpLabel

现代 CSS 支持

// 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!
$vbLabelText   $csharpLabel

迁移清单

迁移前

  • 识别代码库中所有Rotativa的使用情况
  • 文档 CustomSwitches 用于转换为 RenderingOptions
  • 注意页眉/页脚占位符的转换语法([页面]{page}
  • ironpdf.com获取IronPDF许可证密钥

软件包变更

  • 删除RotativaRotativa.AspNetCore NuGet 包
  • 删除 wkhtmltopdf 二进制文件( wkhtmltopdf.exewkhtmltox.dll ) 安装IronPdf NuGet 包

代码更改

  • 更新命名空间导入( using Rotativa;using IronPdf;
  • ViewAsPdf替换为ChromePdfRenderer + RenderHtmlAsPdf()
  • UrlAsPdf替换为RenderUrlAsPdf()
  • CustomSwitches转换为RenderingOptions属性
  • 更新占位符语法([页面]{page}[topage]{总页数}
  • PageMargins替换为单独的MarginTop / MarginBottom / MarginLeft / MarginRight
  • 在适当情况下改为异步模式
  • 在应用程序启动时添加许可证初始化

后迁移

  • 验证所有 PDF 生成功能是否正常
  • 比较 PDF 输出质量(Chromium 的渲染更准确)
  • 验证 CSS 渲染改进(Flexbox/Grid 现在可以正常工作)
  • 测试 JavaScript 执行(现在使用 Chromium 内核已稳定运行)
  • 验证安全扫描通过(不再有CVE-2022-35583标志)
  • 更新 Docker 配置以移除 wkhtmltopdf 安装

Curtis Chau
技术作家

Curtis Chau 拥有卡尔顿大学的计算机科学学士学位,专注于前端开发,精通 Node.js、TypeScript、JavaScript 和 React。他热衷于打造直观且美观的用户界面,喜欢使用现代框架并创建结构良好、视觉吸引力强的手册。

除了开发之外,Curtis 对物联网 (IoT) 有浓厚的兴趣,探索将硬件和软件集成的新方法。在空闲时间,他喜欢玩游戏和构建 Discord 机器人,将他对技术的热爱与创造力相结合。