如何用 C# 从 Rotativa 迁移到 IronPDF
从Rotativa迁移到 IronPDF:完整的 C# 迁移指南
从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的应用程序都会永久暴露。
攻击如何工作
<!-- 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" />影响:
- 访问 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删除 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";IRON VB CONVERTER ERROR developers@ironsoftware.com完整的 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;IRON VB CONVERTER ERROR developers@ironsoftware.com核心类映射
| Rotativa 类 | IronPdf 同等产品 | 备注 |
|---|---|---|
| <代码>ViewAsPdf</代码 | <代码>ChromePdfRenderer</代码 | 渲染 HTML |
| <代码>ActionAsPdf</代码 | <代码>ChromePdfRenderer.RenderUrlAsPdf()</代码 | 渲染 URL |
| <代码>UrlAsPdf</代码 | <代码>ChromePdfRenderer.RenderUrlAsPdf()</代码 | 渲染 URL |
| <代码>方向</代码>枚举 | PdfPaperOrientation 枚举 | 定位 |
| <代码>大小</代码>枚举 | 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
};
}
}
}IRON VB CONVERTER ERROR developers@ironsoftware.comAfter (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!");
}
}
}IRON VB CONVERTER ERROR developers@ironsoftware.com本例展示了基本的架构差异。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
};
}
}
}IRON VB CONVERTER ERROR developers@ironsoftware.comAfter (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!");
}
}
}IRON VB CONVERTER ERROR developers@ironsoftware.comRotativa 的<代码>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]\""
};
}
}
}IRON VB CONVERTER ERROR developers@ironsoftware.comAfter (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!");
}
}
}IRON VB CONVERTER ERROR developers@ironsoftware.comRotativa 使用 CustomSwitches 将命令行参数传递给 wkhtmltopdf,包括带有占位符(如<代码>[页面]</代码和 [toPage] )的页眉和页脚配置。 这种基于字符串的方法容易出错,而且很难在编译时进行验证。
IronPDF 使用强类型的 TextHeaderFooter 对象,该对象具有 CenterText 和 DrawDividerLine 等属性。 占位符语法从<代码>[页面]</代码变为{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 integrationIRON VB CONVERTER ERROR developers@ironsoftware.comIronPDF 将视图渲染与 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
}IRON VB CONVERTER ERROR developers@ironsoftware.com迁移后的新功能
迁移到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");IRON VB CONVERTER ERROR developers@ironsoftware.com数字签名
var signature = new PdfSignature("certificate.pfx", "password");
pdf.Sign(signature);var signature = new PdfSignature("certificate.pfx", "password");
pdf.Sign(signature);IRON VB CONVERTER ERROR developers@ironsoftware.com密码保护
pdf.SecuritySettings.UserPassword = "secret";pdf.SecuritySettings.UserPassword = "secret";IRON VB CONVERTER ERROR developers@ironsoftware.com水印
pdf.ApplyWatermark("<h1 style='color:red; opacity:0.3;'>DRAFT</h1>");pdf.ApplyWatermark("<h1 style='color:red; opacity:0.3;'>DRAFT</h1>");IRON VB CONVERTER ERROR developers@ironsoftware.comPDF/A 存档合规性
pdf.SaveAsPdfA("archive.pdf", PdfAVersions.PdfA3b);pdf.SaveAsPdfA("archive.pdf", PdfAVersions.PdfA3b);IRON VB CONVERTER ERROR developers@ironsoftware.com现代 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!IRON VB CONVERTER ERROR developers@ironsoftware.com迁移清单
迁移前
- [ ] 确定代码库中所有Rotativa的用法
- [用于转换为 RenderingOptions 的文件 CustomSwitches
- [ ] 注意转换时的页眉/页脚占位符语法(
[page]→{page}) - [ ] 从 ironpdf.com 获取IronPDF许可证密钥
软件包变更
- [ ] 删除
Rotativa和Rotativa.AspNetCoreNuGet 软件包 - [ ] 删除 wkhtmltopdf 二进制文件 (
wkhtmltopdf.exe,wkhtmltox.dll) - [ ] 安装
IronPdfNuGet 软件包
代码更改
- [ ] 更新命名空间导入(
使用 Rotativa;→使用 IronPdf;) - [ ] 使用<代码>ChromePdfRenderer</代码+
RenderHtmlAsPdf()替换<代码>ViewAsPdf</代码。 - [将<代码>UrlAsPdf</代码替换为
RenderUrlAsPdf() - [ ] 将
CustomSwitches转换为RenderingOptions属性 - [ ] 更新占位符语法(
[page]→{page},<代码>[topage]</代码→{total-pages}) - [ ] 将
PageMargins替换为单独的MarginTop/MarginBottom/MarginLeft/MarginRight。 - [ ] 酌情改为 async 模式
- [ ] 在应用程序启动时添加许可证初始化
后迁移
- [ ] 验证所有 PDF 生成工作是否正常
- [ ] 比较 PDF 输出质量(Chromium 的渲染更准确)
- [ ] 验证 CSS 渲染的改进(Flexbox/Grid 现在可以工作了)
- [ ] 测试 JavaScript 的执行情况(现在使用 Chromium 时非常可靠)
- [ ] 验证安全扫描通过(不再有CVE-2022-35583标记)
- [ ] 更新 Docker 配置以移除 wkhtmltopdf 安装
结论
虽然Rotativa曾为 ASP.NET MVC 应用程序中的 PDF 生成提供了直接的解决方案,但它对过时技术栈的依赖以及在维护方面的放弃带来了巨大的挑战。 关键安全漏洞CVE-2022-35583将永远无法修补,从而永久暴露每个Rotativa应用程序。
本次迁移的主要变化有 1.安全性:关键 SSRF 漏洞 (CVE-2022-35583) → 已保护 2.架构:仅限 MVC → 任何 .NET 项目类型 3.渲染引擎:过时的 WebKit (2012) → 现代 Chromium 4.CSS 支持:部分(无 Flexbox/网格)→完全 CSS3 支持 5.JavaScript:不可靠 → 完全支持 ES6+ 6.模式:<代码>ViewAsPdf</代码>返回类型 → <代码>ChromePdfRenderer</代码> + <代码>RenderHtmlAsPdf()</代码 7.URL 渲染:<代码>UrlAsPdf</代码> → <代码>RenderUrlAsPdf()</代码 8.页眉/页脚:CustomSwitches 字符串 → 类型 TextHeaderFooter 对象 9.占位符:[page],<代码>[topage]</代码→ {page}, {total-pages} 10.异步支持:仅同步 → 完全异步/等待 11.维护:放弃 → 每周更新
对在各种.NET 应用程序中多功能、安全地生成 PDF 感兴趣的开发人员会发现IronPDFfor .NET 是一个更有说服力的选择。IronPDF 不仅能确保与各种应用程序的兼容性,还能提供持续的支持和更新,这在当今快速发展的技术环境中至关重要。






