跳至頁尾內容
使用IRONPDF

C# 將表單列印為PDF -- 完整開發者指南

並行PDF模板的問題

IronPDF首頁 Razor視圖已經構建完成。 發票明細頁面渲染行項目、計算總計並應用公司的樣式表。 專案狀態頁面顯示任務分解,帶有僅在里程碑過期時顯示的條件部分。 工資單視圖將收入、扣除和年度累計數字格式化為表格,HR團隊花了兩週時間才正確。 所有這些工作都已完成,然後有相關人員要求新增一個"下載為PDF"按鈕。

標準回應是構建第二個模板:一個重現相同佈局的HTML字串或報告定義,適用於PDF路徑。 這第二個模板開始時是第一個模板的副本,然後立即開始偏離。 UI設計師在Sprint 14中更新發票視圖的表格樣式。沒有人更新PDF模板,直到有使用者報告下載的發票看起來與螢幕上的不同。 現在有兩個真相來源,其中一個總是略微錯誤。

使用者端JavaScript PDF函式庫避免了重複,但失去伺服器渲染資料、經過身份驗證的資料、伺服器端計算總計及由ViewModel驅動的條件部分,無法在傳送到瀏覽器端渲染時保留下來。 從伺服器進行無頭瀏覽器自動化是脆弱的,增加了基礎設施負擔,並且在容器化環境中不可預測地失敗。 瀏覽器打 印到PDF僅適用於手動列印的單個使用者; 這不是生產應用程式中的"下載PDF"按鈕。

真實場景顯示了真正的代價:電子商務管理員下載訂單明細頁以便履行,客戶從專案管理工具導出專案狀態頁,員工下載其工資單,調度員列印路線總結。 所有人都希望PDF看起來完全像他們在螢幕上看到的一樣。

解決方案:渲染現有視圖,而不是其副本

IronPDF允許ASP.NET Core應用程式直接將現有Razor視圖(即服務於瀏覽器的同一視圖)渲染為PDF。 PDF控制器動作使用標準視圖引擎將Razor視圖渲染為HTML字串,將該字串傳遞給ChromePdfRenderer.RenderHtmlAsPdf(),並將結果作為文件下載返回。

一個視圖,兩個輸出。 當Razor視圖更改時,PDF輸出會自動隨之更改,無需協調。 沒有並行模板需要維護,沒有需要進行除錯的使用者端變通方案,也沒有需要保持活著的無頭瀏覽器過程。渲染在現有的.NET應用程式中作為單個NuGet包運行。

實際運作方式

1. 視圖已經存在:PDF動作是新內容

位於/invoices/{id}的發票明細頁渲染相同的資料模型,無論是服務於瀏覽器還是生成PDF。 模型包括行項目、總計、客戶詳情和公司標誌,所有視圖需要的資料。 現有的InvoicesController有一個填充該模型的Details動作。 PDF動作是它的兄弟,不是替代品。

當使用者點擊"下載PDF"時,請求命中/invoices/{id}/pdf。 PDF動作使用相同的服務調用來獲取相同的ViewModel,模型是相同的。 不同的是接下來發生的事情。

2. 將Razor視圖渲染為HTML字串

PDF動作不返回ViewResult,而是使用視圖渲染服務調用Razor引擎對視圖文件和ViewModel進行運行,將輸出捕捉為字串。 這是ASP.NET Core中的常見模式,將IViewRenderService注入控制器中,調用ICompositeViewEngine,在一個虛假的ActionContext中執行視圖,並返回渲染的HTML。

渲染的HTML字串是完整的:所有資料已填充,所有條件部分已解析,所有CSS類名都存在。 這是瀏覽器將接收的相同HTML,捕獲於伺服器端。

3. ChromePdfRenderer將HTML字串轉換為PDF格式

using IronPdf;

[HttpGet("{id}/pdf")]
public async Task<IActionResult> DownloadInvoicePdf(int id)
{
    var model = await _invoiceService.GetInvoiceViewModelAsync(id);

    // Render the existing Razor view to an HTML string
    string html = await _viewRenderer.RenderToStringAsync("Invoices/Details", model);

    var renderer = new ChromePdfRenderer();

    renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
    renderer.RenderingOptions.MarginTop = 15;
    renderer.RenderingOptions.MarginBottom = 15;

    PdfDocument pdf = renderer.RenderHtmlAsPdf(html);

    return File(pdf.BinaryData, "application/pdf"
        $"Invoice-{model.InvoiceNumber}.pdf");
}
using IronPdf;

[HttpGet("{id}/pdf")]
public async Task<IActionResult> DownloadInvoicePdf(int id)
{
    var model = await _invoiceService.GetInvoiceViewModelAsync(id);

    // Render the existing Razor view to an HTML string
    string html = await _viewRenderer.RenderToStringAsync("Invoices/Details", model);

    var renderer = new ChromePdfRenderer();

    renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
    renderer.RenderingOptions.MarginTop = 15;
    renderer.RenderingOptions.MarginBottom = 15;

    PdfDocument pdf = renderer.RenderHtmlAsPdf(html);

    return File(pdf.BinaryData, "application/pdf"
        $"Invoice-{model.InvoiceNumber}.pdf");
}
Imports IronPdf
Imports Microsoft.AspNetCore.Mvc

<HttpGet("{id}/pdf")>
Public Async Function DownloadInvoicePdf(id As Integer) As Task(Of IActionResult)
    Dim model = Await _invoiceService.GetInvoiceViewModelAsync(id)

    ' Render the existing Razor view to an HTML string
    Dim html As String = Await _viewRenderer.RenderToStringAsync("Invoices/Details", model)

    Dim renderer As New ChromePdfRenderer()

    renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print
    renderer.RenderingOptions.MarginTop = 15
    renderer.RenderingOptions.MarginBottom = 15

    Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf(html)

    Return File(pdf.BinaryData, "application/pdf", $"Invoice-{model.InvoiceNumber}.pdf")
End Function
$vbLabelText   $csharpLabel

生成的PDF文件

IronPDF範例輸出PDF CssMediaType.Print應用任何@media print規則,這些規則已經在視圖的樣式表中:隱藏導航欄、抑制動作按鈕,並應用列印特定的間距,而無需對Razor視圖本身進行任何更改。

提示如果Razor視圖通過相對路徑引用樣式表或圖像,請在RenderHtmlAsPdf()上將BaseUrlPath設為第二個參數,以便IronPDF在渲染期間正確解析這些資產。 沒有它,CSS和圖像引用在瀏覽器上下文中工作的將無法在伺服器端渲染器中載入。

4. 在不更改視圖的情況下微調PDF輸出

PDF特定的調整,例如頁碼、自定義邊距、帶有文件標題的頁眉,是在渲染器上配置的,而不是在Razor視圖中。 這樣可以將列印邏輯排除在模板之外:

var renderer = new ChromePdfRenderer();

renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    HtmlFragment = @"
        <div style='font-size:9px; color:#888; text-align:center; width:100%;'>
            Invoice — Page {page} of {total-pages}
        </div>",
    DrawDividerLine = true
};

PdfDocument pdf = renderer.RenderHtmlAsPdf(html);
var renderer = new ChromePdfRenderer();

renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4;
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
    HtmlFragment = @"
        <div style='font-size:9px; color:#888; text-align:center; width:100%;'>
            Invoice — Page {page} of {total-pages}
        </div>",
    DrawDividerLine = true
};

PdfDocument pdf = renderer.RenderHtmlAsPdf(html);
Imports IronPdf

Dim renderer As New ChromePdfRenderer()

renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.A4
renderer.RenderingOptions.MarginTop = 20
renderer.RenderingOptions.MarginBottom = 20
renderer.RenderingOptions.HtmlFooter = New HtmlHeaderFooter With {
    .HtmlFragment = "
        <div style='font-size:9px; color:#888; text-align:center; width:100%;'>
            Invoice — Page {page} of {total-pages}
        </div>",
    .DrawDividerLine = True
}

Dim pdf As PdfDocument = renderer.RenderHtmlAsPdf(html)
$vbLabelText   $csharpLabel

輸出PDF文件

具有自定義輸出調整的PDF文件 Razor視圖永遠不需要知道它是渲染到瀏覽器還是到PDF。 控制器動作擁有PDF特定的配置,視圖仍然是純顯示模板。

實際效益

零模板重複。 Razor視圖是文件佈局和內容的唯一真相來源。 瀏覽器和PDF都從同一文件渲染,沒有第二個模板需要維護,也沒有偏離需要糾正。

即時採用。 如果視圖已經存在,PDF導出僅需一個控制器動作即可。 不需要重新設計佈局,重建模板或將條件邏輯移植到不同的渲染系統中。

像素級精確輸出。 基於Chromium的渲染意味著CSS網格、flexbox、網頁字體和媒體查詢都能在PDF中正常工作。 輸出與瀏覽器生成的內容匹配,而不是降級的近似值。

列印特定樣式。 @media print規則已經在視圖的樣式表中控制出現在PDF中的內容:隱藏導航、調整列寬以適合紙張或重新排版內容。 沒有單獨的模板,也沒有內聯列印樣式需要單獨管理。

可維護性。 更新Razor視圖,瀏覽器輸出和PDF輸出都會反映出變化。 沒有第二系統需要協調更新,沒有設計師的更改會到達瀏覽器但沒有到達PDF的風險。

沒有每份文件的成本。 渲染在網頁應用程式中進行內部處理。 沒有外部API調用,沒有使用計量,沒有下載量增加的成本模型。

結語

如果Razor視圖已經構建完成,PDF導出不是一個新功能,而是現有工作的新交付途徑。 同一個模型、同一個視圖、同一個樣式:唯一的新增功能是捕捉視圖的HTML輸出的控制器動作,然後將其通過渲染器返回為文件。

這種架構保持程式碼庫清潔,並讓PDF輸出與瀏覽器永久同步。 IronPDF在ironpdf.com完整處理C#中的PDF生成從渲染HTML到儲存、流媒體和操作文件的全過程。 如果您準備將PDF導出新增到現有的Razor視圖,開始您的免費30天試用,並在發佈功能之前將輸出與目前的瀏覽器渲染進行驗證。

Curtis Chau
技術作家

Curtis Chau擁有Carleton大學的電腦科學學士學位,專精於前端開發,擁有Node.js、TypeScript、JavaScript和React的專業知識。Curtis熱衷於建立直觀且美觀的使用者介面,喜愛使用現代框架並建立結構良好、視覺吸引力的手冊。

除了開發,Curtis對物聯網(IoT)有濃厚的興趣,探索創新的方法來整合硬體和軟體。在空閒時間,他喜歡玩遊戲和建立Discord機器人,結合他對技術的熱愛與創造力。

Iron 支援團隊

我們線上24小時,每週5天。
聊天
電子郵件
給我打電話