跳過到頁腳內容
MIGRATION GUIDES

How to Migrate from Rotativa to IronPDF in C#

從 旋轉 遷移到 IronPDF 可以解決關鍵的安全漏洞,同時實現 PDF 生成工作流程的現代化。 本指南提供了一個完整的、逐步的遷移路徑,消除了已棄用的 wkhtmltopdf 依賴項,實現了對現代 CSS 和 JavaScript 的支持,並提供了超越 ASP.NET MVC 的跨平台相容性。

為什麼要從 旋轉 遷移到 IronPDF

了解輪蟲

Rotativa 長期以來一直是開發人員在 C# 中產生 PDF 的熱門選擇。 它利用wkhtmltopdf工具將 HTML 內容轉換為 PDF 格式。 旋轉 是一個專為 ASP.NET MVC 應用程式設計的開源程式庫。 然而,儘管 旋轉 吸引了大量用戶,但它對過時技術堆疊的依賴也帶來了一些挑戰,這些挑戰可能不會立即被每個開發者所察覺。

Rotativa 的核心功能是提供一種簡單的方法,將 PDF 生成整合到 ASP.NET MVC 專案中,並利用wkhtmltopdf的後端功能。

關鍵安全公告

Rotativa 封裝了 wkhtmltopdf,而 wkhtmltopdf 存在尚未修復的嚴重安全漏洞。

屬性 價值
CVE ID CVE-2022-35583
嚴重程度 評分:9.8/10
攻擊向量 網路
地位 永遠不會修復
做作的 所有 旋轉 版本

wkhtmltopdf 已於 2022 年 12 月正式停止維護。維護者明確表示他們不會修復安全漏洞。 所有使用 旋轉 的應用程式將永久暴露在外。

攻擊原理


<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 wraps wkhtmltopdf,它使用:

  • Qt WebKit 4.8(2012 年版)
  • 不支援 Flexbox
  • 不支援 CSS Grid
  • JavaScript 執行失敗 不支援 ES6+

旋轉 與 IronPDF 對比

特徵 旋轉 IronPDF
專案相容性 僅限 ASP.NET MVC 任何 .NET 專案類型(MVC、Razor Pages、Blazor 等)
維護 積極維護
安全 由於 wkhtmltopdf 依賴項而存在漏洞 (CVE-2022-35583) 定期更新和安全性補丁
HTML渲染 過時的 WebKit 現代鉻
CSS3 部分的 全力支持
Flexbox/Grid 不支援 全力支持
JavaScript 不可靠 完整的 ES6+
Razor Pages 不支援 全力支持
布雷澤 不支援 全力支持
PDF 處理 無法使用 滿的
數位簽名 無法使用 滿的
PDF/A 合規性 無法使用 滿的
異步/等待 僅同步 完全異步
開源 是的,MIT許可證 不,商業許可證

對於計劃在 2025 年和 2026 年採用 .NET 10 和 C# 14 的團隊,IronPDF 提供了 旋轉 無法提供的現代 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

核心類別映射

扶輪社 IronPDF當量 筆記
ViewAsPdf ChromePdfRenderer 渲染 HTML
ActionAsPdf ChromePdfRenderer.RenderUrlAsPdf() 渲染 URL
UrlAsPdf ChromePdfRenderer.RenderUrlAsPdf() 渲染 URL
Orientation枚舉 PdfPaperOrientation枚舉 方向
Size枚舉 PdfPaperSize枚舉 紙張尺寸

頁面佔位符轉換

輪蟲佔位符 IronPDF佔位符
[page] {page}
[topage] {total-pages}
[date] {date}
[time] {time}
[title] {html-title}
[sitepage] {url}

程式碼遷移範例

範例 1:HTML 轉 PDF

之前(旋轉式):

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

            // 旋轉 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>";

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

(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

這個例子展示了架構上的根本差異。 旋轉 要求從 MVC 控制器操作返回ViewAsPdf結果,這可讓您與 ASP.NET MVC 框架綁定。 此模式僅適用於 MVC 請求管道,並且需要 Razor 視圖進行渲染。

IronPDF 可在任何地方運作:控制台應用程式、Web API、Blazor、Razor Pages 或任何 .NET 專案類型。 你使用 HTML 字串呼叫RenderHtmlAsPdf()並儲存結果。 無需MVC控制器,沒有視圖依賴。 請參閱HTML 轉 PDF 文件以取得完整範例。

範例 2:URL 轉 PDF

之前(旋轉式):

// 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()
        {
            // 旋轉 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()
        {
            // 旋轉 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
            };
        }
    }
}
$vbLabelText   $csharpLabel

(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:帶頁碼的頁首和頁尾

之前(旋轉式):

// 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

(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,包括帶有[page][toPage]等佔位符的頁首和頁尾配置。 這種基於字串的方法容易出錯,而且在編譯時很難驗證。

IronPDF 使用強型別的TextHeaderFooter對象,具有CenterTextDrawDividerLine等屬性。 佔位符語法從[page]變成{page} ,從[toPage]變成{total-pages} 。 類型化屬性提供智慧感知、編譯時檢查,且不會出現拼字錯誤。


僅 MVC 架構的問題

Rotativa 是為 ASP.NET MVC 5 及更早版本設計的:

// ❌ 旋轉 - 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
// ❌ 旋轉 - 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 完全支持 async/await:

// ❌ 旋轉 - 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
}
// ❌ 旋轉 - 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 後,您將獲得 旋轉 無法提供的功能:

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

遷移清單

遷移前

  • 識別程式碼庫中所有 旋轉 的使用情況
  • 文檔 CustomSwitches 用於轉換為 RenderingOptions
  • 注意頁首/頁尾佔位符的轉換語法( [page]{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]{page}[topage]{total-pages}
  • 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 對製作直覺且美觀的使用者介面充滿熱情,他喜歡使用現代化的架構,並製作結構良好且視覺上吸引人的手冊。

除了開發之外,Curtis 對物聯網 (IoT) 也有濃厚的興趣,他喜歡探索整合硬體與軟體的創新方式。在空閒時間,他喜歡玩遊戲和建立 Discord bots,將他對技術的熱愛與創意結合。