QuestPDF 是当前最轻量、语法清晰且无需外部依赖的 C# PDF 生成首选方案;它基于 Fluent API 和声明式布局,编译时无反射开销,单个 .dll 即可运行,内置 SkiaSharp 支持系统字体或字节数组加载,中文渲染稳定;而 iText7 需显式注册字体且授权敏感,PdfSharp 不支持 TrueType 嵌入导致中文不可用。

用 C# 生成 PDF 文件,QuestPDF 是当前最轻量、语法清晰且无需外部依赖的首选方案;iTextSharp(v5)已停止维护,itext7 配置繁琐、许可证敏感,不推荐新项目使用。
为什么选 QuestPDF 而不是 iText7 或 PdfSharp
QuestPDF 基于 Fluent API + 声明式布局,代码接近自然语言;编译时无运行时反射开销,生成速度快;单个 .dll 引用即可工作,不依赖系统字体或 Ghostscript。而 iText7 必须显式注册字体(否则中文乱码)、需处理 LicensedFont 或商业授权,PdfSharp 不支持 TrueType 字体嵌入,中文渲染基本不可用。
常见错误现象:System.ArgumentException: Font 'SimSun' is not registered(iText7)、System.NullReferenceException(PdfSharp 加载字体失败)——本质都是字体链断裂,QuestPDF 默认用 SkiaSharp 渲染,直接支持系统字体或字节数组加载。
- 中文场景必须显式注册字体:用
Document.DefaultFont.Size(12).Color(Color.Black).Font("Microsoft YaHei")或更稳妥地FontManager.Register加载本地.ttf文件 - 导出到流而非文件路径时,确保
Stream未被提前关闭(常见于 ASP.NET Core 中返回FileStreamResult时未设leaveOpen: true) - QuestPDF v2024.3+ 默认启用硬件加速,若部署在 Linux Docker 容器中需加
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false并安装libfontconfig1
最小可运行示例:生成带表格和标题的 PDF
只需一个类库项目,NuGet 安装 QuestPDF(最新稳定版),以下代码可直接运行:
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
<p>// 必须调用一次初始化
QuestPDF.Settings.License = LicenseType.Community;</p><p>var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(2, Unit.Centimetre);
page.Header().Text("销售报表").FontSize(18).Bold().AlignCenter();</p><pre class="brush:php;toolbar:false;"> page.Content().Column(column =>
{
column.Item().Text($"生成时间:{DateTime.Now:yyyy-MM-dd HH:mm}");
column.Item().Table(table =>
{
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(1);
columns.RelativeColumn(1);
columns.RelativeColumn(1);
});
table.Header(header =>
{
header.Cell().Text("商品").Bold();
header.Cell().Text("数量").Bold();
header.Cell().Text("单价").Bold();
});
table.Cell().Text("iPhone 15");
table.Cell().Text("2");
table.Cell().Text("¥5999");
table.Cell().Text("MacBook Air");
table.Cell().Text("1");
table.Cell().Text("¥8999");
});
});
});
});
// 输出到文件 document.GeneratePdf("report.pdf");
// 或输出到内存流(用于 Web 返回) using var stream = new MemoryStream(); document.GeneratePdf(stream); stream.Position = 0; // 然后 return File(stream, "application/pdf", "report.pdf");
处理真实业务场景:动态数据 + 页眉页脚 + 页码
当数据来自数据库或 API,需避免在 Document.Create 外部捕获变量导致闭包陷阱(尤其异步场景)。正确做法是把数据作为参数传入委托,或用 Func 构建延迟执行块。
- 页码用
currentPageNumber和totalPagesCount:在page.Footer()中写Text($"第 {currentPageNumber} 页,共 {totalPagesCount} 页") - 页眉固定内容(如公司 Logo)建议用
Image+ 绝对路径或嵌入资源流,避免相对路径在不同环境解析失败 - 表格行数超一页时,
Table默认不重复表头,需手动用table.Header()+table.RepeatAsHeader(true) - 导出大量数据(>5000 行)时,禁用
Document.DebugView(开发时开启便于调试布局),否则性能下降明显
QuestPDF 的核心复杂点不在语法,而在字体注册时机和跨平台字体路径处理——Windows 上 "SimSun" 可用,Linux 容器里必须挂载 /usr/share/fonts/truetype/wqy/wqy-microhei.ttc 并用 FontManager.Register 显式加载,这点容易被忽略。











