在 C# 中生成报表;像 Crystal Reports (.NET 10) Curtis Chau 已更新:2026年1月22日 下载 IronPDF NuGet 下载 DLL 下载 Windows 安装程序 免费试用 法学硕士副本 法学硕士副本 将页面复制为 Markdown 格式,用于 LLMs 在 ChatGPT 中打开 向 ChatGPT 咨询此页面 在双子座打开 向 Gemini 询问此页面 在 Grok 中打开 向 Grok 询问此页面 打开困惑 向 Perplexity 询问有关此页面的信息 分享 在 Facebook 上分享 分享到 X(Twitter) 在 LinkedIn 上分享 复制链接 电子邮件文章 This article was translated from English: Does it need improvement? Translated View the article in English 使用 IronPDF 在 C# .NET 中生成HTML-to-PDF 报表,用标准的 HTML、CSS 和 Razor 模板取代 Crystal Reports 专有的 .rpt 设计器,使 .NET 开发人员能够使用已有的 Web 开发技能构建数据驱动的业务报表。 这包括完全支持动态表格、JavaScript 驱动的图表、条件格式化、多文档批处理以及在任何运行 .NET 的环境中进行跨平台部署。 as-heading:2(TL;DR:快速入门指南) 本教程涵盖用 C# .NET 生成 HTML-to-PDF 报告来取代 Crystal Reports,从基本模板到批处理和计划生成。 适用对象:替换 Crystal Reports 或从头开始构建新报表系统的 .NET 开发人员。 您将构建的内容:三个完整的报告实现(销售发票、员工目录、库存报告),Plus 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 安装 IronPdf(Install-Package IronPdf)。 只需几行代码即可生成您的第一份报告: 立即开始使用 NuGet 创建 PDF 文件: 使用 NuGet 包管理器安装 IronPDF PM > Install-Package IronPdf 复制并运行这段代码。 // 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"); 部署到您的生产环境中进行测试 立即开始在您的项目中使用 IronPDF,免费试用! 免费试用30天 购买或注册 IronPDF 30 天试用版后,请在应用程序的开头添加许可证密钥。 IronPdf.License.LicenseKey = "KEY"; IronPdf.License.LicenseKey = "KEY"; Imports IronPdf IronPdf.License.LicenseKey = "KEY" $vbLabelText $csharpLabel 今天在您的项目中使用 IronPDF,免费试用。 第一步: 免费开始 使用 NuGet 安装 PM > Install-Package IronPdf 在 IronPDF 上查看 NuGet 快速安装。超过 1000 万次下载,它正以 C# 改变 PDF 开发。 您也可以下载 DLL 或 Windows 安装程序。 as-heading:2(目录) TL;DR: 快速入门指南 快速概述 HTML 模板到 PDF 架构 C# 报告生成器:HTML 模板转换为 PDF 为什么要在 .NET 应用程序中替换 Crystal Reports. 不支持 .NET 8 或 .NET Core 复杂的许可和隐藏的成本。 Windows-Only Platform Lock-In 在 .NET 10 中设置 C# 报告生成器 选择一种模板方法:Razor、HTML 或混合。 用 C# 构建数据驱动的 PDF 报告 创建 HTML/CSS 报告模板。 将数据绑定到模板 添加页眉、页脚和页码。 创建动态表格和重复章节。 使用 IronPDF 生成高级 C# 报告。 在 PDF 报告中添加图表。 应用条件格式化和业务逻辑。 创建子报告和章节分隔。 生成目录 从 Crystal Reports 迁移到 IronPDF。 Map Crystal Reports Concepts to IronPDF 转换 .rpt 模板的最佳策略。 在 .NET 中批量生成报告并进行调度。 并行生成多个报告 Integrate Report Generation With ASP.NET Core Background Services 报告生成与 ASP.NET Core 后台服务集成</a 下载完整测试项目 测试项目下载。 C#报告生成器:将 HTML 模板转换为 PDF. HTML 到 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 只能在使用传统 .NET Framework 的 Windows 上运行。 您不能将这些应用程序部署到 Linux 容器、Azure App Service on Linux、AWS Lambda 或 Google Cloud Run。 随着企业使用容器化、平台无关和无服务器系统,这些限制变得更加重要。 构建微服务的开发团队面临着额外的挑战。 如果有九个服务在轻量级 Linux 容器中运行,而 Crystal Reports 需要使用 Windows,那么部署工作就会变得更加复杂。 您需要 Windows 容器镜像、Windows 兼容主机和单独的部署设置。 报告服务成为例外,阻碍了标准化。 在 .NET 10 中设置 C# 和 num; 报告生成器 开始使用 IronPdf 非常简单。 像安装其他 .NET 依赖项一样,通过 NuGet 安装该库。 不需要下载额外的软件,也不需要为生产服务器单独安装运行时安装程序。 选择模板方法:Razor、HTML 或混合。 IronPDF 支持三种不同的报告模板构建方法。 根据团队组成、项目要求和长期维护方面的考虑,每种方法都具有特定的优势。 Razor Views 为已经在 .NET 生态系统中工作的团队提供了最丰富的开发体验。 强类型模型在 Visual Studio 和 VS Code 中具有完整的 IntelliSense 支持、编译时检查以及 C# for 循环、条件、空处理和字符串格式化的全部功能。 Razor 的语法对于那些已经构建了 ASP.NET Core 应用程序的人来说非常熟悉,消除了与其他生态系统的模板引擎相关的学习曲线。 模板与其他源文件一起驻留在项目中,参与重构操作,并作为正常构建流程的一部分进行编译。 Plain HTML with String Interpolation 对于较简单的报告或喜欢将模板与 .NET 代码完全分开的团队非常适用。 HTML 模板可以存储为编译到程序集中的嵌入式资源、与应用程序一起部署的外部文件,甚至可以在运行时从数据库或内容管理系统中检索。基本数据绑定使用 string.Replace() 来处理单个值,或使用 Scriban 或 Fluid 等轻量级模板库来处理更高级的应用场景。 这种方法最大限度地提高了可移植性,允许设计人员在不安装任何 .NET 工具的情况下编辑模板,只需使用文本编辑器和网络浏览器进行预览。 混合方法结合了这两种技术,适用于需要灵活性的场景。 例如,在渲染 Razor 视图时,可能会生成主要的 HTML 结构,然后对不适合视图模型的动态元素进行额外的字符串替换后处理。 另外,还可以加载非开发人员设计的 HTML 模板,并使用 Razor 部分视图在组合所有内容之前,只呈现复杂的数据驱动部分。 HTML 到 PDF 的转换与 HTML 源无关,您可以根据每份报告的需求混合使用不同的方法。 考虑到这些选项,本教程主要侧重于 Razor 视图,因为它们在类型安全性、可维护性和功能丰富度方面为典型的业务报表场景提供了最佳平衡。 如果未来的需求包括使用纯 HTML 模板,则技能可以直接转换,因为这两种方法都能生成 HTML 字符串。 在 C# 中构建数据驱动的 PDF 报告; 本节演示了从头到尾创建销售发票报告的全过程。 该示例涵盖了所有报告使用的基本模式:定义一个数据结构模型,创建一个将数据转换为格式化 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; } // 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 $vbLabelText $csharpLabel 模型中包含小计、税额和总计的计算属性。 这些计算属于模型而不是模板,使 Razor 视图专注于展示,而模型处理业务逻辑。 这种分离使单元测试变得简单明了,可以在不渲染任何 HTML 的情况下验证计算结果。 现在创建 Razor 视图,将此模型转换为专业格式的发票。将其保存为InvoiceTemplate.cshtml,放在你的Views文件夹中: @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"> <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> <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> <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> <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> <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> @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"> <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> <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> <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> <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> <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> 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(); } } 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 $vbLabelText $csharpLabel 对于更简单的场景,当您不需要完整的 ASP.NET Core MVC 设置时,比如在控制台应用程序或后台服务中,您可以只使用带插值的 HTML 字符串和 StringBuilder 来处理动态部分。 输出示例 添加页眉、页脚和页码 专业报告通常包括所有页面一致的页眉和页脚,显示公司品牌、文档标题、生成日期和页码。 IronPDF 提供了两种实现这些元素的方法:基于文本的标题,用于需要最少格式化的简单内容;HTML 标题,用于带有徽标和自定义布局的完全样式控制。 基于文本的标题可以很好地提供基本信息,而且由于不需要额外的 HTML 解析,因此渲染速度更快: :path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/text-headers-footers.cs 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 $vbLabelText $csharpLabel 可用的合并字段包括:{page}表示当前页码;{total-pages}表示文档的总页数;{date}和{time}表示生成时间戳;{url}表示源 URL(如果从网页渲染);{html-title}和{pdf-title}表示文档标题。 这些占位符会在渲染过程中自动替换。 对于带有徽标、自定义字体或复杂多栏布局的标题,请使用支持完整 CSS 样式的 HTML 标题: :path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/html-headers-footers.cs 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 } $vbLabelText $csharpLabel 输出示例 创建动态表格和重复章节 报告通常需要显示跨越多个页面的数据集合。 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 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 $vbLabelText $csharpLabel 部门类上的 CSS 属性page-break-inside: avoid告诉 PDF 渲染器尽可能将部门章节集中在一页上。 如果一个部门的内容会导致中途分页,渲染器会将整个部分移到下一页。 选择器.department:not(:first-child)和page-break-before: always会强制第一个部门之后的每个部门从新页面开始,从而在整个目录中创建清晰的分区。 输出示例 使用 IronPDF 生成高级 C# 报告。 商业报告经常需要超越静态表格和文本的功能。 图表可以直观地反映趋势,而表格的形式则难以理解。 条件格式化可引起对需要采取行动的项目的注意。 子报告将多个来源的数据合并成具有凝聚力的文件。 本节内容包括使用 IronPDF 的 Chromium 渲染引擎实现上述各项功能。 在 PDF 报告中添加图表和图形 由于 JavaScript 在渲染过程中执行,因此您可以使用任何客户端图表库直接在报告中生成可视化效果。 图表将作为页面的一部分进行光栅化处理,并在最终的 PDF 中显示,与屏幕上一模一样。 Chart.js 在简洁性、功能和文档方面取得了极佳的平衡,可满足大多数报告需求。 从 CDN 包含 Chart.js,并使用从 C# 模型序列化的数据配置图表: @model SalesReportModel <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <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> @model SalesReportModel <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <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> HTML 在呈现包含 JavaScript 生成内容的页面时,应配置呈现器,等待脚本执行完毕后再捕获页面: :path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/javascript-wait-rendering.cs 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) $vbLabelText $csharpLabel 输出示例 应用条件格式化和业务逻辑 可视化指示器可立即引起人们对需要采取行动的项目的注意,从而使库存报告受益匪浅。 条件格式化使例外情况一目了然,而不是强迫用户在数百行中寻找问题。 使用 Razor 的内联表达式根据数据值应用 CSS 类: @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"> <span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")"> @item.Quantity </span> </td> </tr> } @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"> <span class="quantity-badge @(item.Quantity <= 5 ? "badge-critical" : "badge-ok")"> @item.Quantity </span> </td> </tr> } HTML 输出示例 创建子报告和分节符 若要将独立生成的报告合并为一个文档,请使用 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; } 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 $vbLabelText $csharpLabel 输出示例 生成目录 IronPDF 可根据 HTML 中的标题元素自动生成目录: :path=/static-assets/pdf/content-code-examples/tutorials/crystal-reports-alternative-csharp/table-of-contents.cs 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") $vbLabelText $csharpLabel 从 Crystal Reports 迁移到 IronPDF. 迁移已建立的报告系统需要精心策划,以尽量减少中断,同时抓住机会实现现代化和简化。 通过了解 Crystal Reports 概念如何映射到基于 HTML 的方法,而不是试图从字面上复制每一个功能或保留原始报表的每一个怪癖,您的工作会进展得更快。 将 Crystal Reports 概念映射到 IronPDF. 了解概念映射有助于系统地翻译现有报告: Crystal Reports IronPdf 同等产品 报告章节 带有 CSS 分页属性的 HTML divs 参数字段 传递给 Razor 视图的模型属性 公式字段 C# 模型类中的计算属性 运行总数 LINQ 聚合 子报告 部分视图或合并 PDF 文档 分组/排序 将数据传递给模板之前的 LINQ 操作 交叉表报告 使用嵌套循环的 HTML 表格 条件格式 使用 CSS 类的 Razor @if 块 转换 .rpt 模板的最佳策略 请勿尝试以编程方式解析 .rpt 文件。 在翻译过程中,我们不能一概而论,而是要将现有的 PDF 输出视为可视化规范,并采用系统化的四步策略重建逻辑: 1.目录:列出所有 .rpt 文件的用途、数据来源和使用频率。 删除过时的报告,以缩小迁移范围。 2.优先考虑:首先迁移高频率报告。针对布局简单或存在持续维护问题的报告。 3.参考文献:将现有 Crystal Reports 导出为 PDF。 将这些内容作为视觉规范供开发人员匹配。 4.验证:使用生产数据量进行测试。 模板在渲染 10 行时速度很快,但在渲染 10,000 行时速度可能会变慢。 .NET 中的批量报告生成和调度 生产系统经常需要同时生成许多报告或按计划运行报告作业。 IronPDF 的线程安全设计可有效支持这两种情况。 并行生成多个报告 对于批处理,请使用 Parallel.ForEachAsync 或使用 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(); } 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 $vbLabelText $csharpLabel 输出示例 批处理示例可并行生成多张发票。 下面是生成的批量发票之一: 将报告生成与 ASP.NET Core 后台服务相结合。 预定报告生成自然地融入了 ASP.NET Core 的托管服务基础架构: // 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 } } } // 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 $vbLabelText $csharpLabel 下载完整测试项目 本教程中的所有代码示例都包含在一个可随时运行的 .NET 10 测试项目中。 下载包括完整的源代码、数据模型、HTML 模板和可生成上述所有 PDF 示例的测试运行程序。 下一步 本指南中的示例展示了 IronPDF可处理各种业务报告需求:简单的发票(包含细列项目和总数)、包含分组数据和照片的复杂员工目录、包含条件格式和图表的库存报告、并行处理数百份文档的批处理,以及通过后台服务计划生成。 如果您正在评估现有 Crystal Reports 实施的替代方案,请从单个高价值报表开始。 使用此处显示的HTML-to-PDF模式重建它,比较开发经验和输出质量,然后再进行扩展。 许多团队发现,在建立模式和基础模板后,他们的第一份转换报告只需要几个小时,而在重复使用 Razor 模板和样式后,后续报告的编写只需要几分钟。 关于布局精度,像素完美呈现指南涵盖了如何使用 CSS 精确匹配 Crystal Reports 输出。 准备好开始构建了吗? 下载 IronPDF 并免费试用。 同一个库可以处理从单个报告渲染到跨 .NET 环境的 大批量批量生成的所有工作。 如果您对迁移报告有疑问或需要架构指导,请联系我们的工程支持团队。 常见问题解答 什么是IronPDF? IronPDF 是一个 C# 库,使开发人员能够以编程方式创建、编辑和生成 PDF 文档,为 Crystal Reports 等传统报表工具提供了一个现代化的替代方案。 IronPdf 如何作为 Crystal Reports 的替代产品? IronPdf 允许开发人员使用 HTML/CSS 模板,从而提供了一种灵活、现代的报表生成方法,这些模板可以轻松地进行样式设计和修改,而 Crystal Reports 的结构则较为僵化。 我可以使用 IronPDF 创建发票吗? 是的,您可以使用 IronPDF 的 HTML/CSS 模板创建详细的定制发票,轻松设计出专业外观的文档。 是否可以用 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 立即与工程团队聊天 技术作家 Curtis Chau 拥有卡尔顿大学的计算机科学学士学位,专注于前端开发,精通 Node.js、TypeScript、JavaScript 和 React。他热衷于打造直观且美观的用户界面,喜欢使用现代框架并创建结构良好、视觉吸引力强的手册。除了开发之外,Curtis 对物联网 (IoT) 有浓厚的兴趣,探索将硬件和软件集成的新方法。在空闲时间,他喜欢玩游戏和构建 Discord 机器人,将他对技术的热爱与创造力相结合。 准备开始了吗? Nuget 下载 17,386,124 | 版本: 2026.2 刚刚发布 免费 NuGet 下载 总下载量:17,386,124 查看许可证