C#如何生成PDF文件?附项目实例与完整源码

来源:Oracle教程作者:过客头衔:草根站长
导读:本期聚焦于过客创作的《C#如何生成PDF文件?附项目实例与完整源码》,敬请观看详情。C#项目里生成PDF时,最容易卡住的往往不是创建文档对象,而是中文字符无法正常显示、表格分页错乱以及部署到不同操作系统后字体行为不一致。如果直接用旧版PdfSharp或简单的HTML转PDF方案,遇到带页眉页脚、动态行数和中文内容的报表就会暴露问题。本文围绕一个可运行的控制台项目展开,说明如何用QuestPDF声明式地生成PDF文件,包括文档模型设计、表格组织、页码添加和字体设置,并附上Program.cs和项目文件完整源码。代码覆盖客户名称、项目编号、明细列表等实际字段,运行后会在程序目录生成一个包含中文表头、内容表格和页码的PDF。即使你之前只用过报表控件,也能按文中的依赖安装和编译步骤快速复现。读完可以直接将示例改造成回执单、发货单或周报导出功能。

在C#应用中生成PDF并不是只有一个标准答案。不同组件在分页控制、字体嵌入、中文支持、许可协议以及部署复杂度上差异很大,选定方案之前首先要确认这些能力是否满足业务要求。本文以一个控制台项目为例,演示用QuestPDF生成一份带页眉、页脚、动态表格和中文内容的PDF文件,并给出完整可编译的代码。示例覆盖项目创建、依赖引用、文档模型定义和输出保存,修改业务字段即可复用到回执单、发货单或周报导出等场景。

C#如何生成PDF文件?附项目实例与完整源码

一、C#生成PDF的常用库如何选择

C#生态里可用的PDF库不少,但定位差异很明显。PdfSharp和MigraDoc提供较低层的绘图与表格能力,轻量且免费,不过要处理中文时需要额外配置字体解析,代码量会随着页面复杂而增加。iText系列功能非常完整,适合大型文档处理,但AGPL许可对闭源商业项目限制较多,引入前要确认合规风险。

QuestPDF采用声明式API,页面结构通过Lambda表达式描述,表头、内容区和页脚分离得很清楚。它基于Skia进行排版,中文支持比早期PdfSharp方案省心,而且分页、页码、自动重复表头等能力可以直接使用。对于大多数需要生成报表、回执和合同预览的业务,QuestPDF是开发效率较高的选择。

库许可特点中文支持适合场景
PdfSharp/MigraDocMIT,可商用需手动处理字体简单绘图、基础表格
iText7AGPL或商业授权较好,需引入字体复杂PDF处理、大型文档
QuestPDF社区版可商用依赖系统字体,配置简单报表、回执、合同预览
商业报表控件通常收费完善企业级报表平台

选择QuestPDF还有一个原因:它把数据、页面和组件组合放在同一个C#文件中即可完成,不需要额外设计模板文件。相比HTML转PDF或报表模板方案,部署时少了一层外部依赖,问题排查也更直接。

二、创建项目并安装QuestPDF

先通过dotnet CLI创建控制台程序。在Windows、Linux或macOS终端执行以下命令即可完成项目初始化和依赖安装。

dotnet new console -n PdfDemo
cd PdfDemo
dotnet add package QuestPDF

安装完成后,需要在程序启动时设置社区许可证。如果不设置,新版QuestPDF会在生成PDF时抛出许可证异常。下面的代码写在Program.cs顶部即可。

using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

许可证声明只需要执行一次。设置完成后,后续所有文档生成都会沿用这个配置。对于内部工具、后台服务和商业产品中的文档导出模块,社区许可通常可以满足基本使用要求,但如果涉及大量分发,建议阅读官方许可说明确认细节。

三、完整项目源码:生成带表格和页码的PDF

下面给出一个完整控制台项目的源码。它定义了一个简单的交付单模型,使用QuestPDF的IDocument接口组织页面头部、内容区和页脚。运行后会生成名为delivery-report.pdf的文件。入口方法准备数据,创建文档实例并调用GeneratePdf,文档类在Compose方法中描述页面结构,这是QuestPDF声明式模型的核心。

using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

QuestPDF.Settings.License = LicenseType.Community;

var data = InvoiceDataSource.GetInvoiceData();
var document = new InvoiceDocument(data);
document.GeneratePdf("delivery-report.pdf");

Console.WriteLine("PDF已生成:delivery-report.pdf");

public class InvoiceDocument : IDocument
{
    public InvoiceData Data { get; }

    public InvoiceDocument(InvoiceData data)
    {
        Data = data;
    }

    public DocumentMetadata GetMetadata() => DocumentMetadata.Empty;

    public void Compose(IDocumentContainer container)
    {
        container.Page(page =>
        {
            page.Size(PageSizes.A4);
            page.Margin(2, Unit.Centimetre);
            page.DefaultTextStyle(x => x.FontSize(10).FontFamily("Microsoft YaHei"));

            page.Header().Column(column =>
            {
                column.Item().Text("项目交付报告").FontSize(20).SemiBold();
                column.Item().Text($"客户:{Data.CustomerName}").FontSize(11);
                column.Item().Text($"项目编号:{Data.ProjectCode}").FontSize(11);
            });

            page.Content().PaddingVertical(1, Unit.Centimetre).Column(column =>
            {
                column.Item().Text("交付明细").FontSize(14).SemiBold();

                column.Item().Table(table =>
                {
                    table.ColumnsDefinition(columns =>
                    {
                        columns.RelativeColumn(3);
                        columns.RelativeColumn(3);
                        columns.RelativeColumn(1);
                    });

                    table.Header(header =>
                    {
                        header.Cell().Background(Colors.Grey.Lighten2).Text("模块");
                        header.Cell().Background(Colors.Grey.Lighten2).Text("说明");
                        header.Cell().Background(Colors.Grey.Lighten2).Text("状态");
                    });

                    foreach (var item in Data.Items)
                    {
                        table.Cell().Text(item.Module);
                        table.Cell().Text(item.Description);
                        table.Cell().Text(item.Status);
                    }
                });
            });

            page.Footer().AlignCenter().Text(text =>
            {
                text.DefaultTextStyle(x => x.FontSize(8));
                text.CurrentPageNumber();
                text.Span(" / ");
                text.TotalPages();
            });
        });
    }
}

public class InvoiceData
{
    public string CustomerName { get; set; } = "";
    public string ProjectCode { get; set; } = "";
    public DateTime DeliveryDate { get; set; }
    public List<InvoiceItem> Items { get; set; } = new();
}

public class InvoiceItem
{
    public InvoiceItem(string module, string description, string status)
    {
        Module = module;
        Description = description;
        Status = status;
    }

    public string Module { get; set; }
    public string Description { get; set; }
    public string Status { get; set; }
}

public static class InvoiceDataSource
{
    public static InvoiceData GetInvoiceData()
    {
        return new InvoiceData
        {
            CustomerName = "北京示例科技有限公司",
            ProjectCode = "PRJ-2025-086",
            DeliveryDate = new DateTime(2025, 3, 18),
            Items = new List<InvoiceItem>
            {
                new("报名模块", "在线报名与审核", "已完成"),
                new("支付模块", "订单创建与退款", "已完成"),
                new("报表模块", "数据导出与汇总", "测试中")
            }
        };
    }
}

项目文件同样很简单,只需要声明.NET版本和QuestPDF包引用。下面给出完整的csproj内容,PackageReference版本在示例中固定为2024.10.0,实际开发时可以替换为当时的稳定版本。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="QuestPDF" Version="2024.10.0" />
  </ItemGroup>

</Project>

把上述两个文件放在同一目录后执行dotnet run,程序会在当前工作目录生成delivery-report.pdf。打开后可以看到A4页面、中文标题、三列明细表格以及底部的页码信息。表格数据来自InvoiceDataSource,可以直接替换为数据库查询结果或接口返回对象。

四、中文显示与字体处理的几个关键点

为什么示例刻意在DefaultTextStyle里设置FontFamily("Microsoft YaHei")?因为PDF不是简单把文字画上去,而是需要嵌入或引用字形数据。Windows系统自带微软雅黑,所以本地调试通常没问题;但如果把程序发布到Linux容器,系统可能没有这个字体,中文字符就会显示成方框或空白。

QuestPDF基于Skia文字排版,字体解析依赖操作系统字体。部署到Linux时,可以安装Noto CJK字体,并把默认字体改为Noto Sans CJK SC。如果目标环境不能安装字体,则应当准备一个允许分发的字体文件,在程序启动时注册字体并从项目资源中加载。这样能保证不同机器上生成效果一致。

另一个常见问题是分页。动态表格行数较多时,QuestPDF会自动处理表头重复和内容分页,但需要在Header中定义表头,并把表格放在Content区域。页脚页码使用CurrentPageNumber和TotalPages可以自动维护,不要自行维护计数器,否则翻页后容易错乱。

最后,生成PDF的目录需要具备写入权限。控制台示例相对简单,但在Web应用中不要把文件保存到应用根目录,建议写入专用临时目录或通过流直接返回给客户端。把项目中的CustomerName、Items等字段替换成实际业务实体,就能快速扩展为回执单、发货单、合同预览等文档导出功能。

C#生成PDFQuestPDFPDF文件生成修改时间:2026-09-18 00:34:05

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0918/58602.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。