// Install-Package IronPdfvar pdf = new IronPdf.ChromePdfRenderer() .RenderHtmlAsPdf("<h1>Sales Report</h1><table><tr><td>Q1</td><td>$50,000</td></tr></table>") .SaveAs("sales-report.pdf");
// 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");
HTML 到 PDF 的生成依赖于线性架构管道。应用程序使用标准数据模型填充 Razor 视图或 HTML 模板,而不是专有文件格式。 生成的HTML字符串然后被传递给渲染引擎,如IronPdf,其将视觉输出捕获为PDF文档。 这种方法将报告设计与托管环境分离开来,使完全相同的代码可以在任何支持 .NET 的平台上执行。
Crystal Reports 只能在使用传统 .NET Framework 的 Windows 上运行。 您不能将这些应用程序部署到 Linux 容器、Azure App Service on Linux、AWS Lambda 或 Google Cloud Run。 随着企业使用容器化、平台无关和无服务器系统,这些限制变得更加重要。
构建微服务的开发团队面临着额外的挑战。 如果有九个服务在轻量级 Linux 容器中运行,而 Crystal Reports 需要使用 Windows,那么部署工作就会变得更加复杂。 您需要 Windows 容器镜像、Windows 兼容主机和单独的部署设置。 报告服务成为例外,阻碍了标准化。
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 字符串。
Build a Data-Driven PDF Report in C#
本节演示了从头到尾创建销售发票报告的全过程。 该示例涵盖了所有报告使用的基本模式:定义一个数据结构模型,创建一个将数据转换为格式化 HTML 的 Razor 模板,将该模板渲染为 HTML 字符串,并将 HTML 转换为 PDF 文档,以便随时查看、通过电子邮件发送或存档。
// Invoice data model with customer, company, and line item detailspublic class InvoiceModel{ public stringInvoiceNumber { get; set; } = string.Empty; public DateTimeInvoiceDate { get; set; } public DateTimeDueDate { get; set; } public CompanyInfoCompany { get; set; } = new(); public CustomerInfoCustomer { get; set; } = new(); public List<LineItem> Items { get; set; } = new(); // Computed totals - business logic stays in the model public decimalSubtotal => Items.Sum(x => x.Total); public decimalTaxRate { get; set; } = 0.08m; public decimalTaxAmount => Subtotal * TaxRate; public decimalGrandTotal => Subtotal + TaxAmount;}// Company details for invoice headerpublic class CompanyInfo{ public stringName { get; set; } = string.Empty; public stringAddress { get; set; } = string.Empty; public stringCity { get; set; } = string.Empty; public stringPhone { get; set; } = string.Empty; public stringEmail { get; set; } = string.Empty; public stringLogoPath { get; set; } = string.Empty;}// Customer billing informationpublic class CustomerInfo{ public stringName { get; set; } = string.Empty; public stringAddress { get; set; } = string.Empty; public stringCity { get; set; } = string.Empty; public stringEmail { get; set; } = string.Empty;}// Individual invoice line itempublic class LineItem{ public stringDescription { get; set; } = string.Empty; public intQuantity { get; set; } public decimalUnitPrice { get; set; } public decimalTotal => 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;
}
ImportsSystemImportsSystem.Collections.GenericImportsSystem.Linq' Invoice data model with customer, company, and line item detailsPublic Class InvoiceModel Public Property InvoiceNumberAsString = String.Empty Public Property InvoiceDateAsDateTime Public Property DueDateAsDateTime Public Property CompanyAs New CompanyInfo() Public Property CustomerAs New CustomerInfo() Public Property ItemsAs New List(OfLineItem)() ' Computed totals - business logic stays in the model PublicReadOnlyPropertySubtotalAsDecimal Get ReturnItems.Sum(Function(x) x.Total)End Get End Property Public Property TaxRateAsDecimal = 0.08D PublicReadOnlyPropertyTaxAmountAsDecimal Get ReturnSubtotal * TaxRateEnd Get End Property PublicReadOnlyPropertyGrandTotalAsDecimal Get ReturnSubtotal + TaxAmountEnd Get End PropertyEnd Class' Company details for invoice headerPublic Class CompanyInfo Public Property NameAsString = String.Empty Public Property AddressAsString = String.Empty Public Property CityAsString = String.Empty Public Property PhoneAsString = String.Empty Public Property EmailAsString = String.Empty Public Property LogoPathAsString = String.EmptyEnd Class' Customer billing informationPublic Class CustomerInfo Public Property NameAsString = String.Empty Public Property AddressAsString = String.Empty Public Property CityAsString = String.Empty Public Property EmailAsString = String.EmptyEnd Class' Individual invoice line itemPublic Class LineItem Public Property DescriptionAsString = String.Empty Public Property QuantityAsInteger Public Property UnitPriceAsDecimal PublicReadOnlyPropertyTotalAsDecimal Get ReturnQuantity * UnitPriceEnd Get End PropertyEnd Class
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 的情况下验证计算结果。
该模板中嵌入的 CSS 可处理所有视觉样式,如颜色、字体、间距和表格格式。 IronPDF 还支持柔性框、网格布局和 CSS 变量等现代 CSS 功能。 渲染后的 PDF 与 Chrome 浏览器的打印预览完全匹配,这使得调试变得简单明了:如果 PDF 中的某些内容看起来不对,请在浏览器中打开 HTML,然后使用开发工具检查并调整样式。
将数据绑定到模板
模型和模板就位后,渲染PDF需要通过IronPDF的ChromePdfRenderer连接它们。 关键步骤是将 Razor 视图转换为 HTML 字符串,然后将该字符串传递给呈现器:
using IronPdf;// Service class for generating invoice PDFs from Razor viewspublic class InvoiceReportService{ private readonly IRazorViewEngine _razorViewEngine; private readonly ITempDataProvider _tempDataProvider; private readonly IServiceProvider _serviceProvider; publicInvoiceReportService( 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 = awaitRenderViewToStringAsync("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();
}
}
ImportsIronPdfImportsMicrosoft.AspNetCore.Mvc.RazorImportsMicrosoft.AspNetCore.Mvc.ViewFeaturesImportsMicrosoft.Extensions.DependencyInjectionImportsMicrosoft.AspNetCore.HttpImportsMicrosoft.AspNetCore.MvcImportsMicrosoft.AspNetCore.RoutingImportsSystem.IOImportsSystem.Threading.Tasks' Service class for generating invoice PDFs from Razor viewsPublic Class InvoiceReportService PrivateReadOnly _razorViewEngine AsIRazorViewEngine PrivateReadOnly _tempDataProvider AsITempDataProvider PrivateReadOnly _serviceProvider AsIServiceProvider Public Sub New(razorViewEngine AsIRazorViewEngine, tempDataProvider AsITempDataProvider, serviceProvider AsIServiceProvider) _razorViewEngine = razorViewEngine _tempDataProvider = tempDataProvider _serviceProvider = serviceProvider End Sub ' Generate PDF from invoice model PublicAsync Function GenerateInvoicePdfAsync(invoice AsInvoiceModel) AsTask(OfByte()) ' Render Razor view to HTML string Dim html AsString = AwaitRenderViewToStringAsync("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 PrivateAsync Function RenderViewToStringAsync(OfTModel)(viewName AsString, model AsTModel) AsTask(OfString) Dim httpContext As New DefaultHttpContextWith {.RequestServices = _serviceProvider} Dim actionContext As New ActionContext(httpContext, New RouteData(), New ActionDescriptor())Using stringWriter As New StringWriter() Dim viewResult = _razorViewEngine.FindView(actionContext, viewName, False) IfNot viewResult.SuccessThenThrow New InvalidOperationException($"View '{viewName}' not found.") End If Dim viewDictionary As New ViewDataDictionary(OfTModel)( 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()EndUsing End FunctionEnd Class
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
using IronPdf;using IronSoftware.Drawing;// Configure text-based headers and footersvar renderer = new ChromePdfRenderer();// Set starting page numberrenderer.RenderingOptions.FirstPageNumber = 1;// Add centered header with divider linerenderer.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 rightrenderer.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/footerrenderer.RenderingOptions.MarginTop = 25;renderer.RenderingOptions.MarginBottom = 20;
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;
ImportsIronPdfImportsIronSoftware.Drawing' Configure text-based headers and footersDim renderer As New ChromePdfRenderer()' Set starting page numberrenderer.RenderingOptions.FirstPageNumber = 1' Add centered header with divider linerenderer.RenderingOptions.TextHeader = New TextHeaderFooterWith { .CenterText = "CONFIDENTIAL - Internal Use Only", .DrawDividerLine = True, .Font = FontTypes.Arial, .FontSize = 10}' Add footer with date on left, page numbers on rightrenderer.RenderingOptions.TextFooter = New TextHeaderFooterWith { .LeftText = "{date} {time}", .RightText = "Page {page} of {total-pages}", .DrawDividerLine = True, .Font = FontTypes.Arial, .FontSize = 9}' Set margins to accommodate header/footerrenderer.RenderingOptions.MarginTop = 25renderer.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 layoutrenderer.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 daterenderer.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};
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
};
ImportsIronPdfImportsSystemDim renderer As New ChromePdfRenderer()' Configure HTML header with logo and custom layoutrenderer.RenderingOptions.HtmlHeader = New HtmlHeaderFooterWith { .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 daterenderer.RenderingOptions.HtmlFooter = New HtmlHeaderFooterWith { .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
}
// Employee directory data modelspublic class EmployeeDirectoryModel{ public List<Department> Departments { get; set; } = new(); public DateTimeGeneratedDate { get; set; } = DateTime.Now;}// Department grouping with manager infopublic class Department{ public stringName { get; set; } = string.Empty; public stringManagerName { get; set; } = string.Empty; public List<Employee> Employees { get; set; } = new();}// Individual employee detailspublic class Employee{ public stringName { get; set; } = string.Empty; public stringTitle { get; set; } = string.Empty; public stringEmail { get; set; } = string.Empty; public stringPhone { get; set; } = string.Empty; public stringPhotoUrl { get; set; } = string.Empty; public DateTimeHireDate { 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 modelsPublic Class EmployeeDirectoryModel Public Property DepartmentsAsList(OfDepartment) = New List(OfDepartment)() Public Property GeneratedDateAsDateTime = DateTime.NowEnd Class' Department grouping with manager infoPublic Class Department Public Property NameAsString = String.Empty Public Property ManagerNameAsString = String.Empty Public Property EmployeesAsList(OfEmployee) = New List(OfEmployee)()End Class' Individual employee detailsPublic Class Employee Public Property NameAsString = String.Empty Public Property TitleAsString = String.Empty Public Property EmailAsString = String.Empty Public Property PhoneAsString = String.Empty Public Property PhotoUrlAsString = String.Empty Public Property HireDateAsDateTimeEnd Class
' 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
由于 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>
@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>
HTML
在呈现包含 JavaScript 生成内容的页面时,应配置呈现器,等待脚本执行完毕后再捕获页面:
using IronPdf;string html = "<h1>Report</h1>";// Configure renderer to wait for JavaScript executionvar renderer = new ChromePdfRenderer();renderer.RenderingOptions.WaitFor.JavaScript(500); // Wait 500ms for JS to completevar pdf = renderer.RenderHtmlAsPdf(html);
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);
ImportsIronPdfDim html AsString = "<h1>Report</h1>"' Configure renderer to wait for JavaScript executionDim renderer As New ChromePdfRenderer()renderer.RenderingOptions.WaitFor.JavaScript(500) ' Wait 500ms for JS to completeDim 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)
<!-- 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>}
<!-- 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>
}
HTML
输出示例
创建子报告和分节符
若要将独立生成的报告合并为一个文档,请使用 IronPDF 的合并功能:
using IronPdf;// Combine multiple reports into a single PDF documentpublic 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;
}
ImportsIronPdf' Combine multiple reports into a single PDF documentPublic Function GenerateCombinedReport(sales AsSalesReportModel, inventory AsInventoryReportModel) AsByte() 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.BinaryDataEnd Function
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 contentsvar renderer = new ChromePdfRenderer();renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbers;var pdf = renderer.RenderHtmlFileAsPdf("report.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");
ImportsIronPdf' Generate PDF with automatic table of contentsDim renderer As New ChromePdfRenderer()renderer.RenderingOptions.TableOfContents = TableOfContentsTypes.WithPageNumbersDim 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")
我最喜欢的这种库是 IronPDF。它允许快速高效地操作 PDF 文件。它还具有许多有价值的功能,比如导出为 PDF/A 格式和数字签名 PDF 文档。
using IronPdf;using System.Collections.Concurrent;// Generate multiple invoices in parallel using async processingpublic async Task<List<ReportResult>> GenerateInvoiceBatchAsync(List<InvoiceModel> invoices){ var results = new ConcurrentBag<ReportResult>(); // Process invoices concurrently with controlled parallelism awaitParallel.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();
}
ImportsIronPdfImportsSystem.Collections.ConcurrentImportsSystem.Threading.Tasks' Generate multiple invoices in parallel using async processingPublicAsync Function GenerateInvoiceBatchAsync(invoices AsList(OfInvoiceModel)) AsTask(OfList(OfReportResult)) Dim results As New ConcurrentBag(OfReportResult)() ' Process invoices concurrently with controlled parallelismAwaitTask.Run(Async Function()AwaitParallel.ForEachAsync(invoices, New ParallelOptionsWith {.MaxDegreeOfParallelism = Environment.ProcessorCount},Async Function(invoice, token) ' Each thread gets its own renderer instance Dim renderer As New ChromePdfRenderer() Dim html AsString = BuildInvoiceHtml(invoice) Dim pdf = Await renderer.RenderHtmlAsPdfAsync(html) ' Save individual invoice PDF Dim filename AsString = $"Invoice_{invoice.InvoiceNumber}.pdf"Await pdf.SaveAsAsync(filename) results.Add(New ReportResultWith {.InvoiceNumber = invoice.InvoiceNumber, .Success = True}) End Function) End Function) Return results.ToList()End Function
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 的托管服务基础架构:
// Background service for scheduled report generationpublic class DailyReportService : BackgroundService{ private readonly IServiceProvider _serviceProvider; protected override async TaskExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // Calculate next run time (6 AM daily) var nextRun = DateTime.Now.Date.AddDays(1).AddHours(6); awaitTask.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
}
}
}
ImportsSystemImportsSystem.ThreadingImportsSystem.Threading.TasksImportsMicrosoft.Extensions.DependencyInjection' Background service for scheduled report generationPublic Class DailyReportServiceInheritsBackgroundService PrivateReadOnly _serviceProvider AsIServiceProviderProtectedOverridesAsync Function ExecuteAsync(stoppingToken AsCancellationToken) AsTask WhileNot stoppingToken.IsCancellationRequested ' Calculate next run time (6 AM daily) Dim nextRun = DateTime.Now.Date.AddDays(1).AddHours(6)AwaitTask.Delay(nextRun - DateTime.Now, stoppingToken) ' Create scoped service for report generationUsing scope = _serviceProvider.CreateScope() Dim reportService = scope.ServiceProvider.GetRequiredService(OfIReportGenerationService)() ' Generate and distribute daily report Dim salesReport = Await reportService.GenerateDailySalesSummaryAsync() ' Email or save reports as neededEndUsingEndWhile End FunctionEnd Class
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 对性能进行了优化,可以通过高效处理 HTML/CSS 并将其渲染为高质量的 PDF 文档来快速生成报告。
Is it possible to automate report generation with IronPDF?
IronPDF supports automation of report generation through batch processing and can be integrated with ASP.NET Core Background Services for scheduled report generation tasks.
What is the best approach to migrate from Crystal Reports to IronPDF?
The best approach involves mapping Crystal Reports concepts to their IronPDF equivalents, prioritizing high-use reports, referencing existing outputs as specifications, and validating the new implementations against production data.