跳至頁尾內容
移民指南

如何在 C# 中將 Rotativa 遷移到 IronPDF

從 旋轉 遷移到 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 IDCVE-2022-35583
嚴重程度評分:9.8/10
攻擊向量網路
地位永遠不會修復
做作的所有 旋轉 版本

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

攻擊原理

<!-- 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" />
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當量筆記
ViewAsPdfChromePdfRenderer渲染 HTML
ActionAsPdfChromePdfRenderer.RenderUrlAsPdf()渲染 URL
UrlAsPdfChromePdfRenderer.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擁有卡爾頓大學電腦科學學士學位,專長於前端開發,精通Node.js、TypeScript、JavaScript和React。他熱衷於打造直覺美觀的使用者介面,喜歡使用現代框架,並擅長撰寫結構清晰、視覺效果出色的使用者手冊。

除了開發工作之外,柯蒂斯對物聯網 (IoT) 也抱有濃厚的興趣,致力於探索硬體和軟體整合的創新方法。閒暇時,他喜歡玩遊戲和製作 Discord 機器人,將他對科技的熱愛與創造力結合。