QuestPDF 入门指南
期望内容
本教程将向您介绍如何使用 QuestPDF 库实现一个基本的发票文档。它会讨论架构概念,然后展示如何准备数据层,并最后演示如何使用各种元素来构建文档结构。完成此教程后,您将获得能够生成类似下图所示的完整、页面感知的发票的代码。
提示: 可以访问 这个 GitHub 存储库 下载、分析和编译代码。
安装
该库作为 NuGet 包分发。您可以像安装其他 NuGet 包一样从您的 IDE 中安装它,搜索关键字 QuestPDF
。有关包详情,请访问 此网页 。
实现层次结构
PDF 生成过程涉及三个主要应用程序层的工作:
文档模型:描述 PDF 文档内容的一组类。通常情况下,它们只是简单的 POCO 类,其中不含任何业务逻辑。
数据源:层面上,您的领域实体被映射到文档模型中。这通常通过创建一个单独的类来实现,该类与持久性抽象进行通信,然后将数据转换成所需的格式。
模板:描述如何呈现信息并将其转换为 PDF 文件的呈现层。 QuestPDF 的方法有所不同:该库为您提供了一个特殊的文档布局引擎。通过使用简单但高度可组合的元素,您可以轻松设计复杂的布局——所有这些都通过强大的链式 API 实现。
文档模型层
在处理新 PDF 文档时,考虑其内容以及应包含哪些信息。这有助于设计合适的模型结构。这次我们需要传递基础发票信息、卖家和客户地址、订单项目列表,最后是可选的评论。
数据源层
一旦定义了模型,就需要创建一个数据源类,该类连接到持久层,准备和转换数据。尽管该层没有限制,您完全控制其实现,但仍有一些模式和实践值得遵循。
首先,在数据源类中,您可以定义所有必要的业务逻辑。简单的操作可以放在模板层(下一章将讨论)中,但更复杂的计算应保留在这里或不适合的服务中,以防止业务逻辑泄漏到域之外。
如果预期有多个具有相似内容的文档,例如相同的标题,定义一个共享模型和相应的填充方法。QuestPDF 遵循 “不要重复自己”(Don't Repeat Yourself,DRY
)原则,提供了强大的组件概念。您可以定义内容元素,注入数据模型以生成适当的内容,甚至可以使用插槽自定义它。这些概念类似于其他流行库,如 Vue
或 Angular
。
本教程主要关注布局结构的准备。因此,所有必要的数据都是随机生成的。
提示: 为了改善工作流程,可以使用各种辅助方法轻松生成假数据。所有这些方法都在静态
Placeholders
类中可用。这样,无需实现真实的数据源,就可以轻松原型化文档结构。
模板层
文档生成的最重要方面是设计和实现其布局。QuestPDF 提供多种工具来实现所需结果。本教程讨论了一些最重要的概念。有关特定元素的更多信息,请访问 API 参考 。
构建页面结构
实现从定义一个新的类开始,该类实现了 IDocument
接口。该接口包含两个方法:GetMetadata()
和 Compose()
。前者用于提供关于作者、关键词、DPI 设置等的基本文档信息,后者则提供一个容器来放置所有内容。
提示: 本教程使用默认元数据配置。如果您想覆盖它,请创建并返回具有适当配置的新
Metadata
对象。大多数属性都很直观。
下面的类实现了基本的文档结构。请注意不同链式 API 调用是如何串联在一起的。每次调用都会创建一个具有相应样式、视觉效果、大小或对齐约束等的单独容器。因此,方法的顺序非常重要,交换元素可能会产生不同的结果。
大多数元素都是单一子项的简单容器,此时使用方法链来描述文档内容。然而,也有一些更高级的元素,它们提供多个插槽来填充。
Page
元素有三个插槽:Header
、Content
和 Footer
。此外,它们还有额外的规则:
Header
始终位于顶部。Footer
始终位于底部。Content
占据剩余空间。
到目前为止,我们已经构建了一个非常简单的页面,每个部分都有不同的颜色或大小:
头部实现
本章介绍了一些重要的布局元素:Row
和 Column
。在讨论它们之前,先看新的代码示例。首先,创建文档时,我们期望它包含多个部分,因此代码量将显著增加。
为了保持代码整洁且易于维护,可以为每个部分创建额外的方法。一般原则是使用简单的布局结构组合,每个方法对应一个结构。大部分 API 调用都有特殊重载,用于 1) 链式调用和 2) 作为参数传递方法。
上面的代码生成如下结果:
内容实现
在文档生成的世界中,预期单个文档有多页。QuestPDF 库假设某些元素应该跨页面重复,例如头部和页脚。此外,它还提供了一种很好的机制来支持分页内容。通常不希望在任何地方拆分内容,通常希望明确指定何时(如果需要)发生分割。
在代码中,内容结构已准备就绪。请注意,评论部分是条件显示的:
表格生成
接下来,我们将介绍 Table
元素。此元素允许您放入多个单元格。
可以使用 Row(X)
和 Column(X)
方法指定单元格的确切位置。然而,默认情况下,位置也可以由算法自动确定。每个单元格还可以占用多行或多列。要指定这种行为,请使用 RowSpan(X)
和 ColumnSpan(X)
方法。
以下是分三步实现表格的简单步骤:
- 步骤 1 定义列数和大小。类似于
Row
元素,你可以创建固定宽度和相对宽度的列。 - 步骤 2 实现表格的表头。这是一个特殊部分:当表格跨多页时,表头内容会出现在每一页上。
- 步骤 3 使用
foreach
循环遍历所有产品,然后为每个产品生成一系列单元格。
地址组件
掌握最后一个技能是代码重用和实现。在创建多个不同的文档类型时,通常它们共享共同的部分,例如带有公司 logo 的头部。有时,你的页面可能有多个具有相同结构但信息不同的部分。此外,有些部分可能非常复杂,应该提取到单独的类中。
为了妥善解决上述所有场景,请使用组件方法。这样,你可以创建独立的、项目特定的元素,便于重用和维护。实现组件从 IComponent
接口开始:
创建组件与将代码提取到单独方法非常相似。这次,分离更加明显,因为你将代码移动到了新的类和文件中,并且可以轻松地为组件提供参数。
下面的代码展示了如何使用新实现的组件:
文档生成
所有部分准备就绪后,生成过程很简单:
提示:你可以通过访问 这个 GitHub 存储库 下载、分析和编译代码。
复杂示例
想要查看使用了大部分可用功能的更高级示例吗?请查看 存储库 。它包含一个样本报告: