C# 將表單列印為PDF -- 完整開發者指南
並行PDF模板的問題
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
生成的PDF文件
CssMediaType.Print應用任何@media print規則,這些規則已經在視圖的樣式表中:隱藏導航欄、抑制動作按鈕,並應用列印特定的間距,而無需對Razor視圖本身進行任何更改。
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)
輸出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天試用,並在發佈功能之前將輸出與目前的瀏覽器渲染進行驗證。




