如何在 C# 中使用 Razor 無頭地將 CSHTML 轉換為 PDF
Fluid 是一個 .NET 程式庫(在 NuGet 上以 Fluid.Core 發行,由 Sebastien Ros 撰寫,遵循 MIT 許可證)實現了 Liquid 模板語言,為開發者提供了一種靈活的方式來渲染動態模板並將內容與顯示邏輯分開。 Fluid 在生成動態文字輸出方面效果很好,但它不直接支持 PDF 生成——開發者必須整合一個額外的 PDF 程式庫來將 HTML 輸出轉換為 PDF 文件。 這種雙程式庫的方法引入了許多開發團隊試圖消除的複雜性。
本指南提供了從 Fluid(模板)與外部 PDF 程式庫遷移到 IronPDF 的完整遷移路徑,包括逐步指導、程式碼比較,並針對評估此轉變的專業 .NET 開發者提供實踐範例。
為什麼從 Fluid(模板)轉至 IronPDF
Fluid 是一個基於 Liquid 的穩定模板引擎,但用它來進行 PDF 生成會引入顯著的複雜性:
雙程式庫依賴: Fluid 只生成 HTML—您需要一個單獨的 PDF 程式庫(如 wkhtmltopdf、PuppeteerSharp 等)來建立 PDF,這增加了您的依賴和維護負擔。
整合複雜性: 協調兩個程式庫意味著要管理兩套配置、錯誤處理和更新。 當出現問題時,除錯變得更加困難。
Liquid 語法學習曲線: 開發者必須學習 Liquid 模板語法 ({{ }}, {% %}),而 C# 已經內建了強大的字串處理功能。
受限的 PDF 控制: 您的 PDF 輸出質量取決於您選擇與 Fluid 搭配使用的任意 PDF 程式庫,而不是一個專用的渲染引擎。
除錯難題: 錯誤可能發生在模板或 PDF 生成階段,使故障排除比單一綜合解決方案更加困難。
執行緒安全性問題: TemplateContext 不支持執行緒安全,並且在並發應用中需要謹慎管理。
IronPDF 與 Fluid(模板):功能比較
了解結構差異有助於技術決策者評估遷移投資:
| 方面 | Fluid + PDF 程式庫 | IronPDF |
|---|---|---|
| 依賴關係 | 2+ 包(Fluid + PDF 程式庫) | 單個包 |
| 模板 | Liquid 語法 ({{ }}) |
C# 字串插值或 Razor |
| PDF 生成 | 需要外部程式庫 | 內建 Chromium 引擎 |
| CSS 支援 | 取決於 PDF 程式庫 | 全部 CSS3 支援,含 Flexbox/Grid |
| JavaScript | 取決於 PDF 程式庫 | 支援完整 JavaScript |
| 執行緒安全 | TemplateContext 不支持執行緒安全 | ChromePdfRenderer 支持執行緒安全 |
| 學習曲線 | Liquid + PDF 程式庫 API | HTML/CSS(網頁標準) |
| 錯誤處理 | 兩個錯誤來源 | 單個錯誤來源 |
快速開始:Fluid 到 IronPDF 遷移
可以立即透過這些基本步驟開始遷移。
步驟 1:替換 NuGet 套件
移除 Fluid 和任何外部 PDF 程式庫:
# Remove Fluid and external PDF library
dotnet remove package Fluid.Core
dotnet remove package WkHtmlToPdf-DotNet # or whatever PDF library you used
dotnet remove package PuppeteerSharp # if used
# Remove Fluid and external PDF library
dotnet remove package Fluid.Core
dotnet remove package WkHtmlToPdf-DotNet # or whatever PDF library you used
dotnet remove package PuppeteerSharp # if used
安裝 IronPDF:
# Install IronPDF (all-in-one solution)
dotnet add package IronPdf
# Install IronPDF (all-in-one solution)
dotnet add package IronPdf
步驟 2:更新命名空間
用 IronPDF 替換 Fluid 命名空間:
// Before (Fluid + external PDF library)
using Fluid;
using Fluid.Values;
using SomeExternalPdfLibrary;
// After (IronPDF)
using IronPdf;
using IronPdf.Rendering; // For RenderingOptions
// Before (Fluid + external PDF library)
using Fluid;
using Fluid.Values;
using SomeExternalPdfLibrary;
// After (IronPDF)
using IronPdf;
using IronPdf.Rendering; // For RenderingOptions
Imports Fluid
Imports Fluid.Values
Imports SomeExternalPdfLibrary
Imports IronPdf
Imports IronPdf.Rendering ' For RenderingOptions
步驟 3:初始化授權
在應用啓動時新增授權初始化:
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY"
程式碼遷移範例
基本 HTML 到 PDF
最基本的操作揭示了這些方法間的關鍵差異。
Fluid 方法:
// NuGet: Install-Package Fluid.Core
using Fluid;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse("<html><body><h1>Hello {{name}}!</h1></body></html>");
var context = new TemplateContext();
context.SetValue("name", "World");
var html = await template.RenderAsync(context);
// Fluid only generates HTML - you'd need another library to convert to PDF
File.WriteAllText("output.html", html);
}
}
// NuGet: Install-Package Fluid.Core
using Fluid;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse("<html><body><h1>Hello {{name}}!</h1></body></html>");
var context = new TemplateContext();
context.SetValue("name", "World");
var html = await template.RenderAsync(context);
// Fluid only generates HTML - you'd need another library to convert to PDF
File.WriteAllText("output.html", html);
}
}
Imports Fluid
Imports System.IO
Imports System.Threading.Tasks
Module Program
Async Function Main() As Task
Dim parser As New FluidParser()
Dim template = parser.Parse("<html><body><h1>Hello {{name}}!</h1></body></html>")
Dim context As New TemplateContext()
context.SetValue("name", "World")
Dim html = Await template.RenderAsync(context)
' Fluid only generates HTML - you'd need another library to convert to PDF
File.WriteAllText("output.html", html)
End Function
End Module
IronPDF 方法:
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var html = "<html><body><h1>Hello World!</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var html = "<html><body><h1>Hello World!</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
}
Imports IronPdf
Imports System
Class Program
Shared Sub Main()
Dim renderer = New ChromePdfRenderer()
Dim html = "<html><body><h1>Hello World!</h1></body></html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("output.pdf")
End Sub
End Class
Fluid 需要建立一個 FluidParser,解析模板字串,建立一個 TemplateContext,為每個變數調用 SetValue(),異步渲染以獲取 HTML,然後寫入文件——這還不是 PDF。 程式碼中的註釋明確指出"Fluid 只生成 HTML - 您需要另一個程式庫來轉換為 PDF。"
IronPDF 消除這種複雜性:建立渲染器,調用 RenderHtmlAsPdf(),並直接保存為 PDF。 無需中間的 HTML 文件,無需額外的程式庫。
如需進階 HTML 到 PDF 的場景,請參見 HTML 到 PDF 轉換指南。
含動態資料的發票模板
含多個變數的文件模板清晰地展示了模板模式差異。
Fluid 方法:
// NuGet: Install-Package Fluid.Core
using Fluid;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse(@"
<html><body>
<h1>Invoice #{{invoiceNumber}}</h1>
<p>Date: {{date}}</p>
<p>Customer: {{customer}}</p>
<p>Total: ${{total}}</p>
</body></html>");
var context = new TemplateContext();
context.SetValue("invoiceNumber", "12345");
context.SetValue("date", DateTime.Now.ToShortDateString());
context.SetValue("customer", "John Doe");
context.SetValue("total", 599.99);
var html = await template.RenderAsync(context);
// Fluid outputs HTML - requires additional PDF library
File.WriteAllText("invoice.html", html);
}
}
// NuGet: Install-Package Fluid.Core
using Fluid;
using System;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse(@"
<html><body>
<h1>Invoice #{{invoiceNumber}}</h1>
<p>Date: {{date}}</p>
<p>Customer: {{customer}}</p>
<p>Total: ${{total}}</p>
</body></html>");
var context = new TemplateContext();
context.SetValue("invoiceNumber", "12345");
context.SetValue("date", DateTime.Now.ToShortDateString());
context.SetValue("customer", "John Doe");
context.SetValue("total", 599.99);
var html = await template.RenderAsync(context);
// Fluid outputs HTML - requires additional PDF library
File.WriteAllText("invoice.html", html);
}
}
Imports Fluid
Imports System
Imports System.IO
Imports System.Threading.Tasks
Module Program
Async Function Main() As Task
Dim parser As New FluidParser()
Dim template = parser.Parse("
<html><body>
<h1>Invoice #{{invoiceNumber}}</h1>
<p>Date: {{date}}</p>
<p>Customer: {{customer}}</p>
<p>Total: ${{total}}</p>
</body></html>")
Dim context As New TemplateContext()
context.SetValue("invoiceNumber", "12345")
context.SetValue("date", DateTime.Now.ToShortDateString())
context.SetValue("customer", "John Doe")
context.SetValue("total", 599.99)
Dim html = Await template.RenderAsync(context)
' Fluid outputs HTML - requires additional PDF library
File.WriteAllText("invoice.html", html)
End Function
End Module
IronPDF 方法:
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var invoiceNumber = "12345";
var date = DateTime.Now.ToShortDateString();
var customer = "John Doe";
var total = 599.99;
var html = $@"
<html><body>
<h1>Invoice #{invoiceNumber}</h1>
<p>Date: {date}</p>
<p>Customer: {customer}</p>
<p>Total: ${total}</p>
</body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("invoice.pdf");
}
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var invoiceNumber = "12345";
var date = DateTime.Now.ToShortDateString();
var customer = "John Doe";
var total = 599.99;
var html = $@"
<html><body>
<h1>Invoice #{invoiceNumber}</h1>
<p>Date: {date}</p>
<p>Customer: {customer}</p>
<p>Total: ${total}</p>
</body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("invoice.pdf");
}
}
Imports IronPdf
Imports System
Module Program
Sub Main()
Dim renderer As New ChromePdfRenderer()
Dim invoiceNumber As String = "12345"
Dim [date] As String = DateTime.Now.ToShortDateString()
Dim customer As String = "John Doe"
Dim total As Double = 599.99
Dim html As String = $"
<html><body>
<h1>Invoice #{invoiceNumber}</h1>
<p>Date: {[date]}</p>
<p>Customer: {customer}</p>
<p>Total: ${total}</p>
</body></html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("invoice.pdf")
End Sub
End Module
Fluid 使用 Liquid 的 {{variable}} 語法,每個變數使用 context.SetValue()。 註釋明確指出"Fluid 輸出 HTML - 需要其他 PDF 程式庫。" IronPDF 使用標準 C# 字串插值$"{variable}"—開發者已知的語法—並直接輸出成 PDF。
欲了解更多文件生成模式,請參見 IronPDF 教程。
使用迴圈的動態資料
帶集合和迴圈的模板展示了控制流的差異。
Fluid 方法:
// NuGet: Install-Package Fluid.Core
using Fluid;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse(@"
<html><body>
<h1>{{title}}</h1>
<ul>
{% for item in items %}
<li>{{item}}</li>
{% endfor %}
</ul>
</body></html>");
var context = new TemplateContext();
context.SetValue("title", "My List");
context.SetValue("items", new[] { "Item 1", "Item 2", "Item 3" });
var html = await template.RenderAsync(context);
// Fluid generates HTML only - separate PDF conversion needed
File.WriteAllText("template-output.html", html);
}
}
// NuGet: Install-Package Fluid.Core
using Fluid;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var parser = new FluidParser();
var template = parser.Parse(@"
<html><body>
<h1>{{title}}</h1>
<ul>
{% for item in items %}
<li>{{item}}</li>
{% endfor %}
</ul>
</body></html>");
var context = new TemplateContext();
context.SetValue("title", "My List");
context.SetValue("items", new[] { "Item 1", "Item 2", "Item 3" });
var html = await template.RenderAsync(context);
// Fluid generates HTML only - separate PDF conversion needed
File.WriteAllText("template-output.html", html);
}
}
Imports Fluid
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading.Tasks
Class Program
Shared Async Function Main() As Task
Dim parser As New FluidParser()
Dim template = parser.Parse("
<html><body>
<h1>{{title}}</h1>
<ul>
{% for item in items %}
<li>{{item}}</li>
{% endfor %}
</ul>
</body></html>")
Dim context As New TemplateContext()
context.SetValue("title", "My List")
context.SetValue("items", New String() {"Item 1", "Item 2", "Item 3"})
Dim html = Await template.RenderAsync(context)
' Fluid generates HTML only - separate PDF conversion needed
File.WriteAllText("template-output.html", html)
End Function
End Class
IronPDF 方法:
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var title = "My List";
var items = new[] { "Item 1", "Item 2", "Item 3" };
var html = $@"
<html><body>
<h1>{title}</h1>
<ul>";
foreach (var item in items)
{
html += $"<li>{item}</li>";
}
html += "</ul></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("template-output.pdf");
}
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var title = "My List";
var items = new[] { "Item 1", "Item 2", "Item 3" };
var html = $@"
<html><body>
<h1>{title}</h1>
<ul>";
foreach (var item in items)
{
html += $"<li>{item}</li>";
}
html += "</ul></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("template-output.pdf");
}
}
Imports IronPdf
Imports System
Imports System.Collections.Generic
Module Program
Sub Main()
Dim renderer As New ChromePdfRenderer()
Dim title As String = "My List"
Dim items As String() = {"Item 1", "Item 2", "Item 3"}
Dim html As String = $"
<html><body>
<h1>{title}</h1>
<ul>"
For Each item As String In items
html += $"<li>{item}</li>"
Next
html += "</ul></body></html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("template-output.pdf")
End Sub
End Module
Fluid 使用 Liquid 的 {% for item in items %}...{% endfor %} 語法—開發者必須學習的一種模板語言。 註釋中提到"Fluid 僅生成 HTML - 需要單獨的 PDF 轉換。" IronPDF 使用標準 C# foreach 迴圈—無需學習新語法—並直接輸出成 PDF。
Fluid API 到 IronPDF 映射參考
此映射透過展示直接的 API 等價加速遷移:
核心類映射
| Fluid 類 | IronPDF 等價 |
|---|---|
FluidParser |
無 |
FluidTemplate |
無 |
TemplateContext |
C# 物件/字串 |
TemplateOptions |
RenderingOptions |
FluidValue |
本機 C# 型別 |
| 外部 PDF 類 | ChromePdfRenderer |
方法映射
| Fluid 方法 | IronPDF 等價 |
|---|---|
new FluidParser() |
new ChromePdfRenderer() |
parser.Parse(source) |
無 |
template.RenderAsync(context) |
renderer.RenderHtmlAsPdf(html) |
context.SetValue("key", value) |
var key = value; |
Liquid 語法到 C# 映射
| Liquid 語法 | C# 等價 |
|---|---|
{{variable}} |
$"{variable}" |
{% for item in items %} |
foreach (var item in items) |
{% if condition %} |
if (condition) |
{{x | 大寫}} |
x.ToUpper() |
{{x | 日期: '%Y-%m-%d'}} |
x.ToString("yyyy-MM-dd") |
{{x | 數字到小數點第2位}} |
x.ToString("F2") |
常見遷移問題及解決方案
問題 1:Liquid 語法轉換
Fluid: 使用 {{variable}} 和 {% control %} 語法。
解決方案: 用 C# 字串插值和控制流替換:
// Liquid: {{name | upcase}}
// C#: $"{name.ToUpper()}"
// Liquid: {% for item in items %}{{item}}{% endfor %}
// C#: foreach (var item in items) { html += $"{item}"; }
// Liquid: {{name | upcase}}
// C#: $"{name.ToUpper()}"
// Liquid: {% for item in items %}{{item}}{% endfor %}
// C#: foreach (var item in items) { html += $"{item}"; }
' Liquid: {{name | upcase}}
' C#: $"{name.ToUpper()}"
Dim result As String = name.ToUpper()
' Liquid: {% for item in items %}{{item}}{% endfor %}
' C#: foreach (var item in items) { html += $"{item}"; }
For Each item In items
html &= item.ToString()
Next
問題 2:TemplateContext 變數
Fluid: 使用 context.SetValue("key", value) 傳遞資料。
解決方案: 使用標準 C# 變數:
// Before (Fluid)
var context = new TemplateContext();
context.SetValue("customer", customerName);
// After (IronPDF)
var customer = customerName;
var html = $"<p>Customer: {customer}</p>";
// Before (Fluid)
var context = new TemplateContext();
context.SetValue("customer", customerName);
// After (IronPDF)
var customer = customerName;
var html = $"<p>Customer: {customer}</p>";
' Before (Fluid)
Dim context As New TemplateContext()
context.SetValue("customer", customerName)
' After (IronPDF)
Dim customer = customerName
Dim html = $"<p>Customer: {customer}</p>"
問題 3:執行緒安全性
Fluid: TemplateContext 不支持執行緒安全,需在並發應用中謹慎管理。
解決方案: ChromePdfRenderer 支持執行緒安全,可以在執行緒間共享:
// Thread-safe usage
private static readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();
public byte[] GeneratePdf(string html)
{
var pdf = _renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}
// Thread-safe usage
private static readonly ChromePdfRenderer _renderer = new ChromePdfRenderer();
public byte[] GeneratePdf(string html)
{
var pdf = _renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}
' Thread-safe usage
Private Shared ReadOnly _renderer As New ChromePdfRenderer()
Public Function GeneratePdf(html As String) As Byte()
Dim pdf = _renderer.RenderHtmlAsPdf(html)
Return pdf.BinaryData
End Function
問題 4:雙階段錯誤處理
Fluid: 錯誤可能發生在模板階段或 PDF 生成階段。
解決方案: IronPDF 有單一錯誤來源:
try
{
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
catch (Exception ex)
{
// Single point of failure—easier debugging
Console.WriteLine($"PDF generation failed: {ex.Message}");
}
try
{
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
catch (Exception ex)
{
// Single point of failure—easier debugging
Console.WriteLine($"PDF generation failed: {ex.Message}");
}
Try
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("output.pdf")
Catch ex As Exception
' Single point of failure—easier debugging
Console.WriteLine($"PDF generation failed: {ex.Message}")
End Try
Fluid 遷移檢查清單
遷移前任務
檢查您的程式碼庫以識別所有 Fluid 使用情況:
# Find all Fluid references
grep -r "FluidParser\|FluidTemplate\|TemplateContext\|using Fluid" --include="*.cs" --include="*.csproj" .
# Find Liquid template files
find . -name "*.liquid" -o -name "*.html" | xargs grep -l "{{"
# Find all Fluid references
grep -r "FluidParser\|FluidTemplate\|TemplateContext\|using Fluid" --include="*.cs" --include="*.csproj" .
# Find Liquid template files
find . -name "*.liquid" -o -name "*.html" | xargs grep -l "{{"
記錄所有模板:文件位置、使用的變數、迴圈和條件,以及外部 PDF 程式庫配置。
程式碼更新任務
- 移除 Fluid.Core NuGet 套件
- 移除外部 PDF 程式庫包
- 安裝 IronPDF NuGet 套件
- 更新名稱空間導入從
Fluid到IronPdf - 將
{{variable}}轉換為$"{variable}" - 將
{% for item in collection %}轉換為 C#foreach - 將
{% if condition %}轉換為 C#if語句 - 將 Liquid 過濾器轉換為 C# 方法(如
| 大寫→.ToUpper() - 替換
FluidParser為ChromePdfRenderer - 用直接的 C# 變數替換
TemplateContext.SetValue() - 移除外部 PDF 程式庫調用
- 在啓動時新增 IronPDF 授權初始化
遷移後測試
遷移後,驗證以下方面:
- 驗證 PDF 輸出符合預期
- 測試所有模板變體是否正確渲染
- 檢查圖片和樣式顯示正確
- 驗證頁面分隔是否正確發生
- 用各種資料大小進行測試
- 性能測試對比 Fluid + 外部程式庫
- 測試在並發場景中的執行緒安全性
清理任務
- 刪除
.liquid模板文件(若不再需要) - 移除與 Fluid 相關的輔助程式碼
- 更新文件
- 清理未使用的依賴項
遷移到 IronPDF 的關鍵好處
從 Fluid(模板)與外部 PDF 程式庫轉至 IronPDF 提供多個重要優勢:
單一套件解決方案: 消除雙程式庫依賴。 IronPDF 同時處理模板(通過 HTML/CSS)和 PDF 生成於一個套件中。
無需學習新語法: 使用標準 C# 字串插值和控制流,而非學習 Liquid 模板語法。
執行緒安全渲染: ChromePdfRenderer 支持執行緒安全,不像 TemplateContext,簡化並發 PDF 生成。
Chromium 渲染引擎: 基於 Chromium 的渲染支持現代 CSS(包括 Flexbox 和 Grid)以及 JavaScript 執行。
單一錯誤來源: 除錯變得更簡單,只需處理一個程式庫,而不是同時協調模板和 PDF 生成階段。
廣泛的 .NET 覆蓋範圍: IronPDF 支援 .NET Framework 4.6.2+、.NET Core 2.0+、.NET Standard 2.0 和 .NET 5 到 .NET 10。

