Generate Reports in C# Like Crystal Reports (.NET 10)
HTML-to-PDF報表生成 在 C# .NET 中使用IronPDF取代Crystal Reports的專有.rpt設計工具,使用標準的HTML、CSS和Razor模板,讓.NET開發者可以運用他們已有的網頁開發技能來構建資料驅動的業務報告。 這包括了對動態表格的完整支持,JavaScript驅動的圖表,條件格式化,多文件批次處理,以及跨平台部署至任何運行.NET的環境。
本教程涵蓋了在C# .NET中用HTML-to-PDF報告生成來取代Crystal Reports,從基本模板到批次處理和排程生成。
- **適合人士:**替換Crystal Reports 或從頭開始構建新報告系統的.NET開發者。
- **您將構建:**三個完整的報告實現(銷售發票、員工名錄、庫存報告),以及Chart.js視覺化圖表、品牌化的頁眉頁腳、目錄生成、子報告合併和并行批次處理。
- 運行環境: .NET 10,.NET 8 LTS,.NET Framework 4.6.2+ 和 .NET Standard 2.0。無需Windows專用COM依賴。
- 何時使用此方法: 當Crystal Reports缺乏.NET Core支持、Windows鎖定,或複雜的授權成為瓶頸時。
- 技術上為何重要: HTML/CSS在各平台上渲染結果一致,與CI/CD整合,並執行圖表的JavaScript,而不需要專有的設計器或按文件計費。
要跟著程式碼範例進行,通過NuGet (Install-Package IronPdf) 安裝IronPdf。 只需幾行程式碼即可生成您的第一份報告:
-
1Install IronPDF with NuGet Package Manager
PM > Install-Package IronPdf
-
2Copy and run this code snippet.
// Install-Package IronPdf var pdf = new IronPdf.ChromePdfRenderer() .RenderHtmlAsPdf("<h1>Sales Report</h1><table><tr><td>Q1</td><td>$50,000</td></tr></table>") .SaveAs("sales-report.pdf");C# -
3Deploy to test on your live environment
Start using IronPDF in your project today with a free trial
在您購買或註冊30天試用版IronPDF後,在應用程式的開始處新增您的授權金鑰。
IronPdf.License.LicenseKey = "KEY";Imports IronPdf
IronPdf.License.LicenseKey = "KEY"Start using IronPDF in your project today with a free trial.
PM > Install-Package IronPdf
- TL;DR: 快速入門指南
- HTML模板到PDF架构
- 為何在.NET應用程式中替換Crystal Reports
- 在.NET 10中設置C#報告生成器
- 在C#中構建資料驅動的PDF報告
- 使用IronPDF進行高級C#報告生成
- 從Crystal Reports遷移到IronPDF
- 在.NET中批次報告生成和排程
- 下載完整測試專案
C# Report Generator: HTML Templates to PDF
HTML-to-PDF的生成依賴於線性架構管道。應用程式使用標準資料模型而不是專有文件格式來填充Razor視圖或HTML模板。 結果的HTML字串然後傳遞給渲染引擎如IronPdf,該引擎將視覺輸出捕獲為PDF文件。 這種方法使報告設計與託管環境解耦,允許完全相同的程式碼在任何支持.NET的平台上運行。
此工作流程與標準網頁開發相似。 前端開發人員使用CSS建立布局,並立即在任何瀏覽器中預覽。 後端開發人員然後使用C#綁定資料。 這種分工允許團隊為报告使用他們現有的版本控制、程式碼審查和持續部署過程,就像他們處理應用程式的其他部分一樣。
HTML提供了Crystal Reports無法實現的功能:交互式圖表、響應式表格以及一致品牌的共享樣式。
為何在.NET應用程式中替換Crystal Reports
從Crystal Reports遷移並不是因為一個巨大的問題或SAP的突然放棄,而是因為多個摩擦點累積,使該平臺在新項目中的使用更加難以合理化,且在現有解決方案的維護中更加困難。 識別這些痛點能夠明確為何許多團隊在尋找替代方案,以及在評估替代選項時最重要的標準。
無.NET 8和.NET Core支持
Crystal Reports不支持.NET Core或.NET 5-10。SAP在論壇上表示,他們不打算新增支持。 SDK使用的是COM元件,這與跨平台的.NET不相容。 支持現代.NET將需要完全重寫,SAP已經拒絕這樣做。
因此,在當前.NET版本上構建新應用程式的團隊無法使用Crystal Reports。 以.NET 8或.NET 10標準化的組織無法整合它。 對於現有應用程式,升級到現代.NET運行時需要先替換報表系統。
複雜的授權和隱藏成本
Crystal Reports的授權區分設計師許可、運行時許可、伺服器部署和嵌入式使用。 規則因桌面、網頁和終端服務不同而異。 某一設置中的合規可能在另一設置中需要額外的許可。 如果在部署後出現了差距,將會出現意外的成本。許多組織認為與其承擔不確定性,不如轉向具有更清晰授權的解決方案。
Windows專用平台鎖定
Crystal Reports只能在Windows上運行,使用傳統的.NET Framework。 您無法將這些應用程式部署到Linux容器、Linux上的Azure應用服務、AWS Lambda或Google Cloud Run。 隨著組織的容器化、平台無關和無伺服器系統的使用,這些限制變得愈加重要。
構建微服務的開發團隊面臨額外挑戰。 如果九個服務運行在輕量級Linux容器中,但其中一個因Crystal Reports需要Windows,則部署更複雜。 您需要Windows容器映像、與Windows相容的託管以及單獨的部署設置。 報告服務成為例外,阻礙標準化。
Set Up a C# Report Generator in .NET 10
開始使用IronPDF很簡單。 通過NuGet像其他.NET依賴項一樣安裝程式庫。 沒有額外的軟體需要下載或是生產伺服器的單獨運行時安裝程式。
選擇模板方案:Razor、HTML或混合
IronPDF支援三種不同的方法來構建報表模板。 每種方法根據團隊組成、專案要求和長期維護考慮提供特定的優勢。
Razor視圖 為已在.NET生態系統中工作的團隊提供最豐富的開發體驗。 在Visual Studio和VS Code中,強型別模型具有完整的IntelliSense支持、編譯時檢查以及C#語言的全功能---如迴圈、條件判斷、空值處理和字串格式化。 Razor的語法對於那些建立了ASP.NET Core應用程式的人來說非常熟悉,無需學習其他生態系統的模板引擎。 模板與其他原始碼檔案一起存在於專案中,參加重構操作,並作為正常構建過程的一部分進行編譯。
純HTML與字元插值 對於較簡單的報告或偏好與.NET程式碼完全分離的團隊效果良好。 HTML模板可以儲存為嵌入式資源,編譯到程式集、部署在應用程式旁邊的外部文件,甚至在運行時從資料庫或內容管理系統中檢索。基本的資料綁定使用string.Replace()來處理單一值,或像Scriban或Fluid等輕量級模板庫來處理更進階的場景。 這一方法最大限度地提高了可攜性,允許設計師在不安裝任何.NET工具的情況下,僅使用文字編輯器和網頁瀏覽器進行預覽來編輯模板。
混合方法 將兩種技術結合起來,以滿足需要靈活性的場景。 例如,Razor視圖可能會被渲染以生成主要HTML結構,然後進行後處理,以進行不適合清洗到視圖模型中的動態元素的附加字串替換。 或者,由非開發人員設計的HTML模板可以被調入,並使用Razor部分視圖來渲染僅包含複雜資料驅動的部分,然後將所有內容組合起來。 HTML-to-PDF的轉換對HTML來源不具有依賴性,允許根據每個報告的需求混合使用不同的方法。
鑒於這些選擇,本教程主要集中於Razor視圖,因為它們在型別安全性、可維護性和功能豐富性中為典型業務報告場景提供了最佳平衡。 如果未來的需求包括使用純HTML模板,這些技能可以直接轉移,因為兩種方法都生成HTML字串。
Build a Data-Driven PDF Report in C#
本節演示了一個完整的從頭到尾的銷售發票報告的建立過程。 範例涵蓋了所有報告使用的基本模式:定義結構資料的模型,建立將資料轉化為格式化HTML的Razor模板,渲染該模板為HTML字串,並將HTML轉換為準備查看、電子郵件或存檔的PDF文件。
建立HTML/CSS報告模板
第一步是定義資料模型。 真實的發票需要客戶資訊、帶有描述和定價的行項、計算的總計、稅務處理和公司品牌元素。 模型類應該結構化以反映這些分組:
// Invoice data model with customer, company, and line item details
public class InvoiceModel
{
public string InvoiceNumber { get; set; } = string.Empty;
public DateTime InvoiceDate { get; set; }
public DateTime DueDate { get; set; }
public CompanyInfo Company { get; set; } = new();
public CustomerInfo Customer { get; set; } = new();
public List<LineItem> Items { get; set; } = new();
// Computed totals - business logic stays in the model
public decimal Subtotal => Items.Sum(x => x.Total);
public decimal TaxRate { get; set; } = 0.08m;
public decimal TaxAmount => Subtotal * TaxRate;
public decimal GrandTotal => Subtotal + TaxAmount;
}
// Company details for invoice header
public class CompanyInfo
{
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string LogoPath { get; set; } = string.Empty;
}
// Customer billing information
public class CustomerInfo
{
public string Name { get; set; } = string.Empty;
public string Address { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
// Individual invoice line item
public class LineItem
{
public string Description { get; set; } = string.Empty;
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public decimal Total => Quantity * UnitPrice;
}Imports System
Imports System.Collections.Generic
Imports System.Linq
' Invoice data model with customer, company, and line item details
Public Class InvoiceModel
Public Property InvoiceNumber As String = String.Empty
Public Property InvoiceDate As DateTime
Public Property DueDate As DateTime
Public Property Company As New CompanyInfo()
Public Property Customer As New CustomerInfo()
Public Property Items As New List(Of LineItem)()
' Computed totals - business logic stays in the model
Public ReadOnly Property Subtotal As Decimal
Get
Return Items.Sum(Function(x) x.Total)
End Get
End Property
Public Property TaxRate As Decimal = 0.08D
Public ReadOnly Property TaxAmount As Decimal
Get
Return Subtotal * TaxRate
End Get
End Property
Public ReadOnly Property GrandTotal As Decimal
Get
Return Subtotal + TaxAmount
End Get
End Property
End Class
' Company details for invoice header
Public Class CompanyInfo
Public Property Name As String = String.Empty
Public Property Address As String = String.Empty
Public Property City As String = String.Empty
Public Property Phone As String = String.Empty
Public Property Email As String = String.Empty
Public Property LogoPath As String = String.Empty
End Class
' Customer billing information
Public Class CustomerInfo
Public Property Name As String = String.Empty
Public Property Address As String = String.Empty
Public Property City As String = String.Empty
Public Property Email As String = String.Empty
End Class
' Individual invoice line item
Public Class LineItem
Public Property Description As String = String.Empty
Public Property Quantity As Integer
Public Property UnitPrice As Decimal
Public ReadOnly Property Total As Decimal
Get
Return Quantity * UnitPrice
End Get
End Property
End Class在模型中包括了GrandTotal的計算屬性。 這些計算屬於模型而不是模板中,保持Razor視圖專注於展示,而模型處理業務邏輯。 這種分離使單元測試變得簡單,可以在不渲染任何HTML的情況下驗證計算。
現在建立將這個模型轉換為專業格式發票的Razor視圖。將其保存在您的Views文件夾中,命名為InvoiceTemplate.cshtml:
@model InvoiceModel
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* Reset and base styles */
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; color: #333; line-height: 1.5; }
.invoice-container { max-width: 800px; margin: 0 auto; padding: 40px; }
/* Header with company info and invoice title */
.header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; padding-bottom: 20px; border-bottom: 2px solid #3498db; }
.company-info h1 { font-size: 24px; color: #2c3e50; margin-bottom: 10px; }
.company-info p { color: #7f8c8d; font-size: 11px; }
.invoice-title { text-align: right; }
.invoice-title h2 { font-size: 32px; color: #3498db; margin-bottom: 10px; }
.invoice-title p { font-size: 12px; color: #7f8c8d; }
/* Address blocks */
.addresses { display: flex; justify-content: space-between; margin-bottom: 30px; }
.address-block { width: 45%; }
.address-block h3 { font-size: 11px; text-transform: uppercase; color: #95a5a6; margin-bottom: 8px; letter-spacing: 1px; }
.address-block p { font-size: 12px; }
/* Line items table */
.items-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }
.items-table th { background-color: #3498db; color: white; padding: 12px; text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
.items-table th:last-child, .items-table td:last-child { text-align: right; }
.items-table td { padding: 12px; border-bottom: 1px solid #ecf0f1; }
.items-table tr:nth-child(even) { background-color: #f9f9f9; }
/* Totals section */
.totals { float: right; width: 300px; }
.totals-row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #ecf0f1; }
.totals-row.grand-total { border-bottom: none; border-top: 2px solid #3498db; font-size: 16px; font-weight: bold; color: #2c3e50; padding-top: 12px; }
/* Footer */
.footer { clear: both; margin-top: 60px; padding-top: 20px; border-top: 1px solid #ecf0f1; text-align: center; color: #95a5a6; font-size: 10px; }
</style>
</head>
<body>
<div class="invoice-container">
<!-- Company header and invoice details -->
<div class="header">
<div class="company-info">
<h1>@Model.Company.Name</h1>
<p>@Model.Company.Address</p>
<p>@Model.Company.City</p>
<p>@Model.Company.Phone | @Model.Company.Email</p>
</div>
<div class="invoice-title">
<h2>INVOICE</h2>
<p>Invoice #: @Model.InvoiceNumber</p>
<p>Date: @Model.InvoiceDate.ToString("MMMM dd, yyyy")</p>
<p>Due Date: @Model.DueDate.ToString("MMMM dd, yyyy")</p>
</div>
</div>
<!-- Bill to address -->
<div class="addresses">
<div class="address-block">
<h3>Bill To</h3>
<p>@Model.Customer.Name</p>
<p>@Model.Customer.Address</p>
<p>@Model.Customer.City</p>
<p>@Model.Customer.Email</p>
</div>
</div>
<!-- Line items table -->
<table class="items-table">
<thead>
<tr><th>Description</th><th>Quantity</th><th>Unit Price</th><th>Total</th></tr>
</thead>
<tbody>
@foreach (var item in Model.Items)
{
<tr>
<td>@item.Description</td>
<td>@item.Quantity</td>
<td>@item.UnitPrice.ToString("C")</td>
<td>@item.Total.ToString("C")</td>
</tr>
}
</tbody>
</table>
<!-- Totals -->
<div class="totals">
<div class="totals-row"><span>Subtotal:</span><span>@Model.Subtotal.ToString("C")</span></div>
<div class="totals-row"><span>Tax (@(Model.TaxRate * 100)%):</span><span>@Model.TaxAmount.ToString("C")</span></div>
<div class="totals-row grand-total"><span>Total Due:</span><span>@Model.GrandTotal.ToString("C")</span></div>
</div>
<!-- Footer message -->
<div class="footer">
<p>Thank you for your business!</p>
<p>Payment is due within 30 days. Please include invoice number with your payment.</p>
</div>
</div>
</body>
</html>
此模板中嵌入的CSS處理所有視覺樣式,例如顏色、字體、間距和表格格式。 IronPDF還支持現代CSS功能,例如彈性框、網格布局和CSS變數。 渲染的PDF與Chrome的列印預覽完全匹配,這使除錯變得簡單:如果PDF中的某些內容看起來不對,打開HTML在瀏覽器中使用開發人員工具檢查和調整樣式。
綁定資料到模板
有了模型和模板之後,渲染PDF需要通過IronPDF的ChromePdfRenderer將它們連接起來。 關鍵步驟是將Razor視圖轉換為HTML字串,然後將該字串傳遞給渲染器:
using IronPdf;
// Service class for generating invoice PDFs from Razor views
public class InvoiceReportService
{
private readonly IRazorViewEngine _razorViewEngine;
private readonly ITempDataProvider _tempDataProvider;
private readonly IServiceProvider _serviceProvider;
public InvoiceReportService(
IRazorViewEngine razorViewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
_razorViewEngine = razorViewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
// Generate PDF from invoice model
public async Task<byte[]> GenerateInvoicePdfAsync(InvoiceModel invoice)
{
// Render Razor view to HTML string
string html = await RenderViewToStringAsync("InvoiceTemplate", invoice);
// Configure PDF renderer with margins and paper size
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.MarginTop = 10;
renderer.RenderingOptions.MarginBottom = 10;
renderer.RenderingOptions.MarginLeft = 10;
renderer.RenderingOptions.MarginRight = 10;
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter;
// Convert HTML to PDF and return bytes
var pdfDocument = renderer.RenderHtmlAsPdf(html);
return pdfDocument.BinaryData;
}
// Helper method to render a Razor view to string
private async Task<string> RenderViewToStringAsync<TModel>(string viewName, TModel model)
{
var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider };
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
using var stringWriter = new StringWriter();
var viewResult = _razorViewEngine.FindView(actionContext, viewName, false);
if (!viewResult.Success)
throw new InvalidOperationException($"View '{viewName}' not found.");
var viewDictionary = new ViewDataDictionary<TModel>(
new EmptyModelMetadataProvider(), new ModelStateDictionary()) { Model = model };
var viewContext = new ViewContext(actionContext, viewResult.View, viewDictionary,
new TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
stringWriter, new HtmlHelperOptions());
await viewResult.View.RenderAsync(viewContext);
return stringWriter.ToString();
}
}Imports IronPdf
Imports Microsoft.AspNetCore.Mvc.Razor
Imports Microsoft.AspNetCore.Mvc.ViewFeatures
Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.AspNetCore.Http
Imports Microsoft.AspNetCore.Mvc
Imports Microsoft.AspNetCore.Routing
Imports System.IO
Imports System.Threading.Tasks
' Service class for generating invoice PDFs from Razor views
Public Class InvoiceReportService
Private ReadOnly _razorViewEngine As IRazorViewEngine
Private ReadOnly _tempDataProvider As ITempDataProvider
Private ReadOnly _serviceProvider As IServiceProvider
Public Sub New(razorViewEngine As IRazorViewEngine, tempDataProvider As ITempDataProvider, serviceProvider As IServiceProvider)
_razorViewEngine = razorViewEngine
_tempDataProvider = tempDataProvider
_serviceProvider = serviceProvider
End Sub
' Generate PDF from invoice model
Public Async Function GenerateInvoicePdfAsync(invoice As InvoiceModel) As Task(Of Byte())
' Render Razor view to HTML string
Dim html As String = Await RenderViewToStringAsync("InvoiceTemplate", invoice)
' Configure PDF renderer with margins and paper size
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.MarginTop = 10
renderer.RenderingOptions.MarginBottom = 10
renderer.RenderingOptions.MarginLeft = 10
renderer.RenderingOptions.MarginRight = 10
renderer.RenderingOptions.PaperSize = IronPdf.Rendering.PdfPaperSize.Letter
' Convert HTML to PDF and return bytes
Dim pdfDocument = renderer.RenderHtmlAsPdf(html)
Return pdfDocument.BinaryData
End Function
' Helper method to render a Razor view to string
Private Async Function RenderViewToStringAsync(Of TModel)(viewName As String, model As TModel) As Task(Of String)
Dim httpContext As New DefaultHttpContext With {.RequestServices = _serviceProvider}
Dim actionContext As New ActionContext(httpContext, New RouteData(), New ActionDescriptor())
Using stringWriter As New StringWriter()
Dim viewResult = _razorViewEngine.FindView(actionContext, viewName, False)
If Not viewResult.Success Then
Throw New InvalidOperationException($"View '{viewName}' not found.")
End If
Dim viewDictionary As New ViewDataDictionary(Of TModel)(
New EmptyModelMetadataProvider(), New ModelStateDictionary()) With {.Model = model}
Dim viewContext As New ViewContext(actionContext, viewResult.View, viewDictionary,
New TempDataDictionary(actionContext.HttpContext, _tempDataProvider),
stringWriter, New HtmlHelperOptions())
Await viewResult.View.RenderAsync(viewContext)
Return stringWriter.ToString()
End Using
End Function
End Class對於較簡單的場景,當您不需要完整的ASP.NET Core MVC設置,例如在控制台應用程式或背景服務中時,您可以僅使用帶有插值的HTML字串和StringBuilder來處理動態部分。
範例輸出
新增頁眉、頁腳和頁碼
專業報告通常在所有頁面上包含一致的頁眉和頁腳,顯示公司品牌、文件標題、生成日期和頁碼。 IronPDF提供了兩種方法來實現這些元素:基於文字的頁眉適合需要最少格式化的簡單內容,而HTML頁眉可對帶有標誌和自定義佈局的全風格進行完全控制。
基於文字的頁眉對於基本資訊的效果良好並且渲染速度更快,因為它們不需要額外的HTML解析:
using IronPdf;
using IronSoftware.Drawing;
// Configure text-based headers and footers
var renderer = new ChromePdfRenderer();
// Set starting page number
renderer.RenderingOptions.FirstPageNumber = 1;
// Add centered header with divider line
renderer.RenderingOptions.TextHeader = new TextHeaderFooter
{
CenterText = "CONFIDENTIAL - Internal Use Only",
DrawDividerLine = true,
Font = FontTypes.Arial,
FontSize = 10
};
// Add footer with date on left, page numbers on right
renderer.RenderingOptions.TextFooter = new TextHeaderFooter
{
LeftText = "{date} {time}",
RightText = "Page {page} of {total-pages}",
DrawDividerLine = true,
Font = FontTypes.Arial,
FontSize = 9
};
// Set margins to accommodate header/footer
renderer.RenderingOptions.MarginTop = 25;
renderer.RenderingOptions.MarginBottom = 20;Imports IronPdf
Imports IronSoftware.Drawing
' Configure text-based headers and footers
Dim renderer As New ChromePdfRenderer()
' Set starting page number
renderer.RenderingOptions.FirstPageNumber = 1
' Add centered header with divider line
renderer.RenderingOptions.TextHeader = New TextHeaderFooter With {
.CenterText = "CONFIDENTIAL - Internal Use Only",
.DrawDividerLine = True,
.Font = FontTypes.Arial,
.FontSize = 10
}
' Add footer with date on left, page numbers on right
renderer.RenderingOptions.TextFooter = New TextHeaderFooter With {
.LeftText = "{date} {time}",
.RightText = "Page {page} of {total-pages}",
.DrawDividerLine = True,
.Font = FontTypes.Arial,
.FontSize = 9
}
' Set margins to accommodate header/footer
renderer.RenderingOptions.MarginTop = 25
renderer.RenderingOptions.MarginBottom = 20可用的合併域包括{pdf-title}表示文件標題。 這些佔位符在渲染期間會自動替換。
對於帶有標誌、定制字體或複雜多列格式的頁眉,使用支持完整CSS樣式的HTML頁眉:
using IronPdf;
using System;
var renderer = new ChromePdfRenderer();
// Configure HTML header with logo and custom layout
renderer.RenderingOptions.HtmlHeader = new HtmlHeaderFooter
{
MaxHeight = 30,
HtmlFragment = @"
<div style='display: flex; justify-content: space-between; align-items: center;
width: 100%; font-family: Arial; font-size: 10px; color: #666;'>
<img src='logo.png' style='height: 25px;'>
<span>Company Name Inc.</span>
<span>Invoice Report</span>
</div>",
BaseUrl = new Uri(@"C:\assets\images\").AbsoluteUri
};
// Configure HTML footer with page info and generation date
renderer.RenderingOptions.HtmlFooter = new HtmlHeaderFooter
{
MaxHeight = 20,
HtmlFragment = @"
<div style='text-align: center; font-size: 9px; color: #999;
border-top: 1px solid #ddd; padding-top: 5px;'>
Page {page} of {total-pages} | Generated on {date}
</div>",
DrawDividerLine = false
};Imports IronPdf
Imports System
Dim renderer As New ChromePdfRenderer()
' Configure HTML header with logo and custom layout
renderer.RenderingOptions.HtmlHeader = New HtmlHeaderFooter With {
.MaxHeight = 30,
.HtmlFragment = "
<div style='display: flex; justify-content: space-between; align-items: center;
width: 100%; font-family: Arial; font-size: 10px; color: #666;'>
<img src='logo.png' style='height: 25px;'>
<span>Company Name Inc.</span>
<span>Invoice Report</span>
</div>",
.BaseUrl = New Uri("C:\assets\images\").AbsoluteUri
}
' Configure HTML footer with page info and generation date
renderer.RenderingOptions.HtmlFooter = New HtmlHeaderFooter With {
.MaxHeight = 20,
.HtmlFragment = "
<div style='text-align: center; font-size: 9px; color: #999;
border-top: 1px solid #ddd; padding-top: 5px;'>
Page {page} of {total-pages} | Generated on {date}
</div>",
.DrawDividerLine = False
}範例輸出
建立動態表格和重複區段
報告通常需要顯示跨多頁的資料集合。 Razor的迴圈結構自然地處理此情況,通過迭代集合生成每個項目的表格行或卡片元素。
這是一個完整的員工名錄範例,展示了按部門分組的資料展示:
// Employee directory data models
public class EmployeeDirectoryModel
{
public List<Department> Departments { get; set; } = new();
public DateTime GeneratedDate { get; set; } = DateTime.Now;
}
// Department grouping with manager info
public class Department
{
public string Name { get; set; } = string.Empty;
public string ManagerName { get; set; } = string.Empty;
public List<Employee> Employees { get; set; } = new();
}
// Individual employee details
public class Employee
{
public string Name { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Phone { get; set; } = string.Empty;
public string PhotoUrl { get; set; } = string.Empty;
public DateTime HireDate { get; set; }
}' Employee directory data models
Public Class EmployeeDirectoryModel
Public Property Departments As List(Of Department) = New List(Of Department)()
Public Property GeneratedDate As DateTime = DateTime.Now
End Class
' Department grouping with manager info
Public Class Department
Public Property Name As String = String.Empty
Public Property ManagerName As String = String.Empty
Public Property Employees As List(Of Employee) = New List(Of Employee)()
End Class
' Individual employee details
Public Class Employee
Public Property Name As String = String.Empty
Public Property Title As String = String.Empty
Public Property Email As String = String.Empty
Public Property Phone As String = String.Empty
Public Property PhotoUrl As String = String.Empty
Public Property HireDate As DateTime
End Class使用在部門類上的CSS屬性page-break-inside: avoid 告訴PDF渲染器儘量將部門區段保留在同一頁上。 如果某一部門的內容會導致頁面中段分隔,渲染器會將整個區段移到下一頁。 使用選擇器.department:not(:first-child) 和 page-break-before: always強制第一個部門之後的每個部門以新頁開始,創造整個名錄的乾淨區段分隔。
範例輸出
Advanced C# Report Generation With IronPDF
業務報告通常需要超出靜態表格和文字的能力。 圖表可視化在表格式中理解起來繁瑣的趨勢。 條件格式化使需要行動的項目一目了然。 子報告將來自多個來源的資料結合到一致的文件中。 本節涵蓋了如何使用IronPDF的Chromium渲染引擎實現這些功能。
將圖表新增到PDF報告中
由於JavaScript在渲染過程中執行,您可以使用任何客戶端的圖表庫直接在報告中生成可視化圖表。 圖表作為頁面的一部分被光柵化,並在最終PDF中顯示,與螢幕上的顯示完全相同。 Chart.js為最常見報告需求提供了出色的簡單性、能力和文件平衡。
從CDN中包含Chart.js,使用從您的C#模型中序列化出的資料來配置圖表:
@model SalesReportModel
<!-- Include Chart.js library from CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Canvas element for chart rendering -->
<canvas id="salesChart"></canvas>
<script>
// Initialize bar chart with data from C# model
const ctx = document.getElementById('salesChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
// Serialize model data to JavaScript arrays
labels: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthLabels)),
datasets: [{
label: 'Monthly Sales',
data: @Html.Raw(System.Text.Json.JsonSerializer.Serialize(Model.MonthlySales)),
backgroundColor: 'rgba(52, 152, 219, 0.7)'
}]
}
});
</script>
在渲染包含JavaScript生成內容的頁面時,配置渲染器以等待腳本完成執行再抓取頁面:
using IronPdf;
string html = "<h1>Report</h1>";
// Configure renderer to wait for JavaScript execution
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.WaitFor.JavaScript(500); // Wait 500ms for JS to complete
var pdf = renderer.RenderHtmlAsPdf(html);Imports IronPdf
Dim html As String = "<h1>Report</h1>"
' Configure renderer to wait for JavaScript execution
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.WaitFor.JavaScript(500) ' Wait 500ms for JS to complete
Dim pdf = renderer.RenderHtmlAsPdf(html)範例輸出
應用條件格式化和業務邏輯
存貨報告得益於立即吸引注意需要行動的項目的可視指標。 條件格式化使例外情況在視覺上顯而易見,而不是強迫使用者掃描數百行以尋找問題。 使用Razor的內聯表達式根據資料值應用CSS類:
<!-- Iterate through inventory items sorted by quantity -->
@foreach (var item in Model.Items.OrderBy(x => x.Quantity))
{
// Apply CSS class based on stock level thresholds
var rowClass = item.Quantity <= Model.CriticalStockThreshold ? "stock-critical" :
item.Quantity <= Model.LowStockThreshold ? "stock-low" : "";
<tr class="@rowClass">
<td>@item.SKU</td>
<td>@item.ProductName</td>
<td class="text-right">
<!-- Badge styling for critical stock items -->
<span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")">
@item.Quantity
</span>
</td>
</tr>
}
範例輸出
建立子報告和分段
要將獨立生成的報告合併為一個文件,使用IronPDF的合併功能:
using IronPdf;
// Combine multiple reports into a single PDF document
public byte[] GenerateCombinedReport(SalesReportModel sales, InventoryReportModel inventory)
{
var renderer = new ChromePdfRenderer();
// Render each report section separately
var salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales));
var inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory));
// Merge PDFs into one document
var combined = PdfDocument.Merge(salesPdf, inventoryPdf);
return combined.BinaryData;
}Imports IronPdf
' Combine multiple reports into a single PDF document
Public Function GenerateCombinedReport(sales As SalesReportModel, inventory As InventoryReportModel) As Byte()
Dim renderer As New ChromePdfRenderer()
' Render each report section separately
Dim salesPdf = renderer.RenderHtmlAsPdf(RenderSalesReport(sales))
Dim inventoryPdf = renderer.RenderHtmlAsPdf(RenderInventoryReport(inventory))
' Merge PDFs into one document
Dim combined = PdfDocument.Merge(salesPdf, inventoryPdf)
Return combined.BinaryData
End Function範例輸出
生成目錄
IronPDF可以基於HTML中的標題元素自動生成目錄:
using IronPdf;
// Generate PDF with automatic table of contents
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers;
var pdf = renderer.RenderHtmlFileAsPdf("report.html");Imports IronPdf
' Generate PDF with automatic table of contents
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers
Dim pdf = renderer.RenderHtmlFileAsPdf("report.html")從Crystal Reports遷移到IronPDF
遷移已建立的報告系統需要謹慎計劃,以在最小化中斷的同時抓住現代化和簡化的機會。 通過理解Crystal Reports概念和基於HTML方法之間的映射,您將更快前進,而不是試圖字面地複製每一個功能或保留原始報告的每個癖好。
將Crystal Reports概念映射到IronPDF
理解概念映射有助於您系統地翻譯現有報告:
| Crystal Reports | IronPDF對等 |
|---|---|
| 報告區段 | 帶有CSS分頁屬性的HTML div |
| 參數域 | 傳遞給Razor視圖的模型屬性 |
| 公式域 | 模型類中的C#計算屬性 |
| 運行總計 | LINQ聚合 |
| 子報告 | 部分視圖或合併的PDF文件 |
| 分組/排序 | 在將資料傳遞給模板前的LINQ操作 |
| 交叉報告 | 使用巢狀迴圈的HTML表格 |
| 條件格式化 | 帶有CSS類的Razor @if 塊 |
轉換.rpt模板的最佳策略
請勿嘗試以程式方式解析.rpt文件。 相反,將現有的PDF輸出視為視覺規範,並使用系統的四步策略重建邏輯:
-
清單整理: 記錄所有.rpt文件及其用途、資料來源和使用頻率。 刪除過時報告以減少遷移範圍。
-
優先化: 優先遷移高頻報表。針對簡單佈局或持續維護問題的報告進行處理。
-
參考: 將現有的Crystal Reports匯出為PDF。 使用這些作為開發人員配對的視覺規範。
-
驗證: 用生產資料量進行測試。 模板在10行時渲染即時,但在10,000行時可能會變慢。
在.NET中批次報告生成和排程
生產系統通常需要同時生成多個報告,或按計劃運行報告作業。 IronPDF的執行緒安全設計有效地支持這兩種情況。
並行生成多個報告
對於批次處理,使用Task.WhenAll:
using IronPdf;
using System.Collections.Concurrent;
// Generate multiple invoices in parallel using async processing
public async Task<List<ReportResult>> GenerateInvoiceBatchAsync(List<InvoiceModel> invoices)
{
var results = new ConcurrentBag<ReportResult>();
// Process invoices concurrently with controlled parallelism
await Parallel.ForEachAsync(invoices,
new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
async (invoice, token) =>
{
// Each thread gets its own renderer instance
var renderer = new ChromePdfRenderer();
string html = BuildInvoiceHtml(invoice);
var pdf = await renderer.RenderHtmlAsPdfAsync(html);
// Save individual invoice PDF
string filename = $"Invoice_{invoice.InvoiceNumber}.pdf";
await pdf.SaveAsAsync(filename);
results.Add(new ReportResult { InvoiceNumber = invoice.InvoiceNumber, Success = true });
});
return results.ToList();
}Imports IronPdf
Imports System.Collections.Concurrent
Imports System.Threading.Tasks
' Generate multiple invoices in parallel using async processing
Public Async Function GenerateInvoiceBatchAsync(invoices As List(Of InvoiceModel)) As Task(Of List(Of ReportResult))
Dim results As New ConcurrentBag(Of ReportResult)()
' Process invoices concurrently with controlled parallelism
Await Task.Run(Async Function()
Await Parallel.ForEachAsync(invoices,
New ParallelOptions With {.MaxDegreeOfParallelism = Environment.ProcessorCount},
Async Function(invoice, token)
' Each thread gets its own renderer instance
Dim renderer As New ChromePdfRenderer()
Dim html As String = BuildInvoiceHtml(invoice)
Dim pdf = Await renderer.RenderHtmlAsPdfAsync(html)
' Save individual invoice PDF
Dim filename As String = $"Invoice_{invoice.InvoiceNumber}.pdf"
Await pdf.SaveAsAsync(filename)
results.Add(New ReportResult With {.InvoiceNumber = invoice.InvoiceNumber, .Success = True})
End Function)
End Function)
Return results.ToList()
End Function範例輸出
批次處理範例並行生成多個發票。 這是生成的批次發票之一:
將報告生成與ASP.NET Core背景服務整合
排程報告生成自然適合於ASP.NET Core's 的託管服務架構:
// Background service for scheduled report generation
public class DailyReportService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Calculate next run time (6 AM daily)
var nextRun = DateTime.Now.Date.AddDays(1).AddHours(6);
await Task.Delay(nextRun - DateTime.Now, stoppingToken);
// Create scoped service for report generation
using var scope = _serviceProvider.CreateScope();
var reportService = scope.ServiceProvider.GetRequiredService<IReportGenerationService>();
// Generate and distribute daily report
var salesReport = await reportService.GenerateDailySalesSummaryAsync();
// Email or save reports as needed
}
}
}Imports System
Imports System.Threading
Imports System.Threading.Tasks
Imports Microsoft.Extensions.DependencyInjection
' Background service for scheduled report generation
Public Class DailyReportService
Inherits BackgroundService
Private ReadOnly _serviceProvider As IServiceProvider
Protected Overrides Async Function ExecuteAsync(stoppingToken As CancellationToken) As Task
While Not stoppingToken.IsCancellationRequested
' Calculate next run time (6 AM daily)
Dim nextRun = DateTime.Now.Date.AddDays(1).AddHours(6)
Await Task.Delay(nextRun - DateTime.Now, stoppingToken)
' Create scoped service for report generation
Using scope = _serviceProvider.CreateScope()
Dim reportService = scope.ServiceProvider.GetRequiredService(Of IReportGenerationService)()
' Generate and distribute daily report
Dim salesReport = Await reportService.GenerateDailySalesSummaryAsync()
' Email or save reports as needed
End Using
End While
End Function
End Class下載完整測試專案
本教程的所有程式碼範例均可從一個準備運行的.NET 10測試專案中獲得。 下載包括完整的原始碼、資料模型、HTML模板和一個測試運行器,用於生成以上所示的所有範例PDF。
後續步驟
本指南中的範例說明了IronPDF處理全方位的業務報告需求:簡單的發票包含行項和總計、複雜的員工名錄帶有分組資料和照片、庫存報告配有條件格式化和圖表、數百個文件的批量處理,以及通過背景服務的排程生成。
如果您在評估替代現有Crystal Reports實現的選擇,請從一個高價值報表開始。 使用此處顯示的HTML-to-PDF模式重建它,比較開發體驗和輸出質量,然後擴展。 許多團隊發現,第一個轉換的報告需要幾小時,因為他們建立了模式和基本模板,而隨後的報告只需幾分鐘來組裝,因為他們重用Razor 模板和樣式。 對於佈局精確度,像素完美渲染指南涵蓋了如何使用CSS將Crystal Reports的輸出精確匹配。
準備好開始構建了嗎? 下載IronPDF並嘗試免費試用。 同一個庫處理從單個報告渲染到跨.NET環境的大批生成。 如果您對報告遷移有疑問或需要架構指導,聯繫我們的工程支援團隊。
Frequently Asked Questions
什麼是IronPDF?
IronPDF是一個C#程式庫,使開發者能夠以程式化的方式建立、編輯和生成PDF文件,為傳統報告工具如Crystal Reports提供了一個現代替代方案。
IronPDF如何作為Crystal Reports的替代方案?
IronPDF提供了一種靈活和現代的報告生成方法,允許開發者使用HTML/CSS模板,這些模板可以輕鬆地樣式化和修改,與Crystal Reports的結構相比更具彈性。
我可以使用IronPDF建立發票嗎?
是的,您可以使用HTML/CSS模板與IronPDF建立詳細且自定義的發票,輕鬆設計出專業外觀的文件。
使用IronPDF生成員工名錄是否可行?
絕對可以。IronPDF允許您利用動態資料和HTML/CSS生成全面的員工名錄,便於清晰有序的呈現。
IronPDF如何幫助庫存報告?
IronPDF可以通過使用HTML/CSS模板簡化庫存報告的建立,這些模板可以動態填充資料,以提供最新且視覺上有吸引力的報告。
在IronPDF中使用HTML/CSS模板有哪些優勢?
在IronPDF中使用HTML/CSS模板可提供設計上的靈活性、易於更新,以及與網頁技術的相容性,使報告佈局更易於維護和增強。
IronPDF支持.NET 10嗎?
是的,IronPDF與.NET 10相容,確保開發者能利用最新.NET功能和改進進行報告生成。
IronPDF如何改善報告生成速度?
IronPDF經過性能優化,允許快速生成報告,透過有效處理HTML/CSS並將其渲染為高質量PDF文件。

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.