恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
C#无Office环境用DocX操作Word文档:源码拆解、核心API与转PDF实战
首页
资讯中心
/
C#无Office环境用DocX操作Word文档:源码拆解、核心API与转PDF实战
C#无Office环境用DocX操作Word文档:源码拆解、核心API与转PDF实战
发布时间:2026/10/18 4:36:22
简介面向需要在服务端或客户端免安装Office环境操作Word文档的.NET开发者这套C# DocX资料整合了开源库源码与可运行demo覆盖文档创建、内容编辑、样式调整、图片表格插入、模板替换以及一键转换PDF等日常办公自动化需求。压缩包约4.93MB内含244个文件以83个C#源码文件、86个docx示例文档为主同时提供7个dll运行库、sln/csproj工程文件、编译缓存及说明文档方便直接编译调试与对照阅读。已有277人学习包内亲测可用的Xceed.Document.NET.dll和Xceed.Words.NET.dll与完整案例对正在做文档自动化的开发者有直接参考价值可省去自行排查环境与依赖的时间。通过源码与demo可快速掌握动态生成报告、合同以及批量转换PDF的实现思路理解Word文档元素的对象模型与API调用方式并能基于现有代码二次开发提升实际项目中的Word处理效率。1. DocX 源码包拆解C# 无 Office 环境编辑 Word 的最短路径做工具软件的工程师大多撞过这堵墙客户要求自动生成 Word 报表服务器上却不允许装 Office。第一反应是 COM 调用 Word但运维不给装COM 这条路直接断掉。后来我拿到一份 DocX 源码包里面带着 Xceed.Document.NET.dll 和 Xceed.Words.NET.dll 两个核心库外加可直接运行的 demo才把这块硬骨头啃下来。DocX 是 C# 环境里读写 .docx 的开源类库核心价值是纯托管代码操作 Word 文档不需要本机安装 Microsoft Office也不依赖 COM 组件。环境里装不装 Word它都能新建段落、插入表格、替换文本、设置样式生成的文件能被 WPS 和 Office 正常打开。这份资源适合做报表生成、批量文书处理、文档自动化管线的开发者也适合想绕过 Office 环境限制、把文档生成逻辑写进服务端的工程师。这篇笔记把这套源码和 demo 完整拆一遍DLL 怎么引、最短代码怎么跑、核心编辑操作落哪些 API、转 PDF 的边界在哪、以及我实际翻车时踩过的坑。2. DLL 引用与第一个可运行 Demo从引用方式到输出 Hello.docx2.1 源码包里先看什么DLL 与 Demo 的工程结构解压资源后别急着把整个文件夹拖进解决方案先花两分钟看清目录结构。这个包里通常包含四块内容DocX 库的 C# 源码目录、一个可运行的 demo 工程、两个核心 DLL 文件Xceed.Document.NET.dll 和 Xceed.Words.NET.dll以及 demo 输出用的示例 docx 文档。源码目录的价值在于运行报错时可以直接跳进库内部看逻辑不需要靠猜demo 工程的价值在于它是这个 DLL 版本下最可靠的调用范例比网上零散片段准得多。动手前我习惯做三件事顺序固定能省掉后面大半的排查时间。第一右键点击两个 DLL 文件打开属性看“详细信息”记录下产品版本和文件版本——这个版本号直接决定你的目标框架能不能兼容老版本 DLL 跑在 .NET 6 上有时候会出 IOException。第二打开 demo 工程的 .csproj 文件看 TargetFramework 写的是哪个版本这通常就是 DLL 作者验证过的环境照抄最稳。第三确认两个 DLL 最终都出现在输出目录里调试时不带的话运行时直接报找不到程序集这类问题后面单独说。文件/目录作用使用方式Xceed.Document.NET.dll核心库提供 DocX 入口和文档对象模型添加引用Xceed.Words.NET.dll核心库的关联程序集承担文档格式实现与上一个 DLL 放置在同一目录源码目录DocX 库的 C# 实现可调试可学习需要时附加到解决方案Demo 工程可运行的示例项目覆盖常见 API 调用先跑通再照葫芦画瓢这个包的实用之处在于源码齐全库内部对 OpenXML 文件包的处理逻辑都摆在眼前。调试时按 F11 能进到库实现里看它怎么解压 document.xml、怎么写段落这种可观测性是纯 NuGet 包给不了的。从一个可正常编译运行的 demo 开始改远比从零搭环境快。2.2 引用 DLL 的三种方式和工程配置把 DLL 接进项目的做法有三种按我自己的使用频次排序第一是直接添加引用在解决方案资源管理器里右键“引用”点“添加引用”切到“浏览”标签选中 Xceed.Words.NET.dll 就行Xceed.Document.NET.dll 会自动跟着被识别。第二是把两个 DLL 放进项目根目录下的 lib 文件夹用 csproj 的 HintPath 显示指明位置适合团队协作时统一路径避免每个人引用的 DLL 来自不同位置。第三是走 NuGet包名是 DocX它能拉取依赖并自动做版本管理但如果手里的项目已经有这两个 DLL 文件就完全没必要再从包源拿一份版本不一致反而会埋雷。我一般选择第二种因为源码包里自带的 DLL 和源码是配套的用 HintPath 锁住这份 DLL能防止别人误用别的版本。下面的 csproj 片段是实际工程里可以用的写法ItemGroup Reference IncludeXceed.Words.NET HintPath..\lib\Xceed.Words.NET.dll/HintPath PrivateTrue/Private /Reference Reference IncludeXceed.Document.NET HintPath..\lib\Xceed.Document.NET.dll/HintPath PrivateTrue/Private /Reference /ItemGroup说明一下这里几个节点的作用HintPath 告诉编译器去指定位置寻找 DLLPrivate 设为 True 表示运行时把 DLL 复制到输出目录这样发布后的程序集文件夹里会自动带上这两个文件不至于在别的机器上触发“找不到程序集”。如果项目用的是 .NET Framework 2.0/4.x 或 .NET Core 3.1直接引用基本不会出怪问题如果是 .NET 5 以上建议先在 demo 工程里把同样的代码跑一遍确认这套版本的 DLL 在目标框架下不抛异常再迁到业务工程里。引用的代码写法上也有一点需要注意。文件顶部要把两个命名空间都 using 进来否则编译器不认识 DocX 类型using Xceed.Words.NET; using Xceed.Document.NET;两个命名空间缺一个都会导致类型解析失败。前者负责文档模型类后者提供部分基础类型和枚举具体到代码里基本都用 DocX 作为入口但 using 不能省。2.3 跑通最小 Demo无 Office 生成第一个 docx环境配好后第一个目标不是做复杂报表而是用最短代码生成一个能正常打开的 Hello.docx验证 DLL 在当前工程里真的可用。下面是控制台程序里最小可用的一版using System; using System.Drawing; using Xceed.Words.NET; using Xceed.Document.NET; class Program { static void Main() { string filePath D:\temp\Hello.docx; using (DocX doc DocX.Create(filePath)) { Paragraph title doc.InsertParagraph(Hello DocX) .FontSize(20) .Bold() .Color(Color.Red); title.Alignment Alignment.center; doc.InsertParagraph(这是一份由 DocX 生成的示例文档当前系统未安装 Office。); doc.Save(); } Console.WriteLine(生成完成: filePath); } }这段代码的核心逻辑分三步DocX.Create 负责在指定路径创建空白文档对象InsertParagraph 追加一个新段落并返回 Paragraph 实例链式调用设置字号 20 磅、加粗、红色Save 把内存中的文档结构落盘。这里一定要用 using 包裹 DocX 实例因为 DocX 实现了 IDisposable内部持有文件流不释放的话文件会被一直占用第二次运行就会报文件被另一进程使用。FontSize 的单位是磅20 磅对应 Word 里的一号字附近Color 来自 System.Drawing不要漏掉 using System.Drawing。Alignment.center 是段落对齐枚举DocX 提供的值有 left、center、right 和 both注意是 .NET 风格的小驼峰命名。跑完这条命令到 D:\temp 下找到 Hello.docx双击用 Word 或 WPS 打开看到居中的红色标题和一行正文说明这条链路已经完全打通。到这里资源和工程之间已经建立起了稳定的连接接下来做复杂操作就是在这个对象模型上继续加元素而不是再折腾环境。如果这一步打开文件时报错先看运行目录下有没有两个 DLL再看目标框架与 DLL 版本是否匹配这两项占了此类问题的大半。3. DocX 核心对象实战段落、表格、图片与样式的编辑路径3.1 段落与格式从 InsertParagraph 到 Append 的层级关系DocX 的文档模型是分层的Document 包含多个 ParagraphParagraph 内部由 Run 组成。用 Word 的直观概念理解Run 就是一段连续格式的文本片段。InsertParagraph 创建的是空段落要真正往里填内容需要调用段落上的 Append 方法每次 Append 可以带上独立的格式设置这样同一个段落里前几个词加粗、后几个词变斜体就能分开控制。下面这段代码演示了在一个段落里混排不同格式的文字using (DocX doc DocX.Create(D:\temp\Report.docx)) { Paragraph p doc.InsertParagraph(); p.Append(注意).FontSize(12).Bold().Color(Color.Red); p.Append(本节数据来自测试环境仅用于功能验证。) .FontSize(10.5f) .Italic() .Color(Color.Black); p.Alignment Alignment.left; p.LineSpacingAfter 12f; doc.Save(); }逻辑上第一次 Append 写入的是“注意”这个引导词格式设置为 12 磅、加粗、红色第二次 Append 追加的是说明正文字号 10.5 磅对应中文排版中的五号字、斜体、黑色。两个 Append 在同一个 Paragraph 对象上执行最终体现在 Word 里是同一个段落内两种不同字形的混排效果。LineSpacingAfter 控制段后间距值越大段落间的空隙越大单位按磅理解即可在需要堆叠大量段落时这个参数能把版面拉开避免文字挤成一团。各 Append 的返回值都是 Paragraph所以链式调用可以一直写下去但要注意链条里一旦某个方法返回类型变了后面就不能继续接段落方法了——比如 Append 返回对象上不再有 InsertParagraph 可调。3.2 表格操作插入、合并单元格、设置边框表格是报表类文档的主力元素。DocX 的 InsertTable 可以直接指定行列数拿到 Table 对象后通过 Rows 和 Cells 索引访问单元格再往 Paragraphs 里写内容。单元格本身自带一个或多个 Paragraph所以往单元格写文本的路径是 table.Rows[i].Cells[j].Paragraphs[0].Append(...)这三层索引是新手最容易写错的地方。实际项目里常见的是制造一个多行多列的表格并且要对局部单元格做合并代码如下Table table doc.InsertTable(3, 4); table.Rows[0].Cells[0].Paragraphs[0].Append(项目); table.Rows[0].Cells[1].Paragraphs[0].Append(计划值); table.Rows[0].Cells[2].Paragraphs[0].Append(实际值); table.Rows[0].Cells[3].Paragraphs[0].Append(偏差); // 纵向合并第一列的前两行 table.Rows[0].Cells[0].MergeCells(table.Rows[1].Cells[0]); table.SetBorder(TableBorderType.Top, 1f, Color.Black); table.SetBorder(TableBorderType.Bottom, 1f, Color.Black); table.AutoFit TableAutoFitBehavior.AutoFitToWindow; doc.InsertTable(0).Insert(table); doc.Save();MergeCells 方法的参数是目标单元格调用后会吃掉两行同一列之间的分隔线做成 Word 里常见的跨行单元格效果。这里有个容易踩到的点合并前第一行和第二行各自有独立单元格对象合并后再通过 Rows[1].Cells[0] 访问时返回的可能是空对象或已被合并的占位单元所以合并操作要放在内容写入之前。SetBorder 用于设置边框第一个参数是边框类型枚举里有 Top、Bottom、Left、Right、InsideH、InsideV 等第二个参数是线宽单位是磅第三个是颜色。AutoFit 三种取值中 AutoFitToWindow 让表格宽度自动匹配页面可排版宽度AutoFitToContents 按内容撑开Fixed 表示固定布局报表场景下优先用 AutoFitToWindow不然列宽会忽宽忽窄。3.3 图片插入与页眉页脚让文档更像成品生成文档里经常要放公司 logo 或者截图DocX 对图片的处理路径和文本不同。AddImage 方法先把图片文件读入文档资源CreatePicture 再按指定宽高生成可插入的图片对象最后 Append 到段落里。尺寸参数的单位是像素传入的是图片在页面上的显示尺寸。using (DocX doc DocX.Create(D:\temp\WithLogo.docx)) { using (Image image DocX.AddImage(D:\temp\logo.png)) { Picture picture image.CreatePicture(40, 160); doc.InsertParagraph().Append(picture); } Footer footer doc.Footers[FooterType.Default]; footer.InsertParagraph(内部资料 请勿外传).Alignment Alignment.center; doc.Save(); }逻辑上AddImage 是静态方法把外部图片加载进文档的资源列表CreatePicture 的两个参数分别是高和宽单位像素实际应用中要注意保持图片原始宽高比否则会拉伸变形。Footers 索引器的入参 FooterType.Default 代表默认节页脚还有 FirstPage 和 EvenPages 可选分别作用于首页和偶数页。页脚的文本会出现在文档每一页底部对需要统一落款、行文编号的场景很实用。DocX 同样支持 Headers 对象写法完全一致把 Footer 换成 Header 即可。3.4 查找替换与模板变量填充报表自动化的核心打法在实际业务里文档结构往往是固定的变化的是其中的数据。DocX 的 ReplaceText 方法可以完成占位符替换这也是我把这套库接进业务系统的关键原因。约定俗成的做法是模板里写 {{变量名}} 这种带花括号的锚点生成文档时用真实值替换doc.ReplaceText({{name}}, 张三); doc.ReplaceText({{date}}, DateTime.Now.ToString(yyyy-MM-dd)); doc.ReplaceText({{amount}}, 12,800.50);ReplaceText 的默认行为是只替换第一处匹配。如果同一个变量在文档里出现多次且都要替换需要改成带 options 参数的重载传入 ReplaceTextOptions.All。另外 DocX 提供 FindUniqueByPattern 方法支持用正则表达式查找文本比如批量找到所有 {{ 开头、}} 结尾的片段再遍历处理。用 {{}} 这种带边界的锚点比裸词替换安全得多避免正文里恰好出现同名词语被误改——这是模板替换里最常见的翻车现场。除了上面这些DocX 还支持设置页面边距、添加超链接、插入复选框、编辑页眉页脚中的图片等。核心操作体系基本就是段落、表格、图片、替换这四类把这一层 API 练熟绝大多数文档生成需求都能覆盖。4. 把 DocX 转 PDF无 Office 与有 Office 两条路及边界4.1 先说结论DocX 本身不导出 PDFDocX 只负责读写 docx 格式它的模型层操作的是 OOXML 内容并不包含 PDF 渲染引擎所以 DocX.Create 出来的文档无论调用什么方法都找不到 ExportAsPdf 之类的原生能力。有些资料里提到老版本的 Dll 里有 SaveAsPdf那是特定魔改版或者经过扩展的库标准实现没有这个 API。转 PDF 必须借助外部手段常见三条路方案依赖适用场景注意点调用 Word COM本机安装 Office有桌面环境的单机程序进程残留、并发能力差LibreOffice headless安装 LibreOffice服务端批量转换第一次启动慢字体缺失时排版偏移在线转换 API外部服务不允许或不适合本地转文档外传有保密风险这三条路里服务端首选 LibreOffice因为它无需图形界面、支持命令行批量转换也不像 COM 那样容易把 Word 进程卡死在内存里。但如果目标机器明确允许安装 OfficeCOM 方案的排版保真度更高使用时要做好资源释放。下一个小节重点讲 LibreOffice 的实际操作。4.2 无 Office 环境LibreOffice headless 批量转换安装 LibreOffice 后soffice.exe 会出现在安装目录的 program 文件夹下。转换是通过命令行参数完成的核心是 --headless --convert-to pdf。我在 C# 服务端工程里的调用方式是启动一个独立进程等它退出后检查输出文件是否生成public bool ConvertToPdf(string docxPath, string outDir) { string sofficePath C:\Program Files\LibreOffice\program\soffice.exe; ProcessStartInfo psi new ProcessStartInfo { FileName sofficePath, Arguments $--headless --norestore --convert-to pdf --outdir \{outDir}\ \{docxPath}\, UseShellExecute false, CreateNoWindow true }; using (Process p Process.Start(psi)) { p.WaitForExit(30000); if (p.HasExited) { string pdfPath Path.Combine(outDir, Path.GetFileNameWithoutExtension(docxPath) .pdf); return File.Exists(pdfPath); } p.Kill(); return false; } }逻辑是拼装 soffice 的完整命令行通过 Process 启动等待最多 30 秒超时则强制结束并返回 false。参数里 --headless 表示无界面运行--norestore 表示不恢复上次会话--convert-to pdf 指定输出格式--outdir 指定输出目录后接的引号是为了兼容路径中的空格。这个方案可以批量循环执行多次调用但要注意并发场景下多个 soffice 同时启动可能争抢配置目录导致转换失败。常见做法是在每次调用时额外带上 -env:UserInstallation指定一个独立的临时配置目录类似 file:///D:/temp/lo_profile1 这样避免配置锁冲突。这个方案的短板在字体和无边框渲染上。如果服务器没装中文字体docx 里的宋体、黑体可能会被替换成默认字体排版行距变样如果表格边框被设置成无边框LibreOffice 为保证可读性有时会自动补边框导致视觉上多出线条。所以转 PDF 的环境建议装有和目标机器一致的中文字体转换前用固定测试文档做一遍排版对照。4.3 有 Office 环境COM 方案与导出参数如果目标机器装了 Office且场景是低频转换COM 是简单直接的选择。C# 里通过互操作程序集调用 Word 应用代码量不大但每一步都要注意释放资源using Word Microsoft.Office.Interop.Word; Word.Application word new Word.Application { Visible false }; Word.Document doc null; try { doc word.Documents.Open(docxPath, ReadOnly: true); doc.ExportAsFixedFormat(pdfPath, Word.WdExportFormat.wdExportFormatPDF); } finally { doc?.Close(false); word.Quit(); System.Runtime.InteropServices.Marshal.ReleaseComObject(doc); System.Runtime.InteropServices.Marshal.ReleaseComObject(word); }代码思路是启动一个不可见的 Word 实例以只读方式打开文档调用 ExportAsFixedFormat 导出 PDF。ExportAsFixedFormat 的第一个参数是输出路径第二个参数指定导出格式为 PDF除此之外还可以传第三个参数表示导出范围、第七个参数控制是否输出文档属性标记。finally 块里做清理关闭文档、退出 Word、释放 COM 对象这三步缺一不可。常见翻车点是只调 Quit 不释放 COM 对象导致任务管理器里 WINWORD.EXE 常驻不退出批量跑几次内存就上去了。要注意COM 这条路的排版保真度最高但前提是系统里安装的确实是 MS Office。如果机器装了 WPSDocuments.Open 可能被 WPS 的 COM 注册项劫持打开后导出行为变得不可控。这种情况下要么在运行转换的进程里指定 Word 的 ProgID要么干脆改走 LibreOffice 方案。COM 还有个隐藏问题Word 首次打开文档时会执行宏或加载项如果模板带有异常内容会导致打开缓慢或弹窗阻塞通常建议 ReadOnly 打开加 Visible 为 false 规避大部分弹窗。5. 避坑 / 常见问题排查DLL 引用、版本与中文乱码实测记录5.1 运行时错误找不到 Xceed.Words.NET.dll 或 Xceed.Document.NET.dll现象程序编译通过但运行到 DocX.Create 附近就抛 FileNotFoundException异常信息里明确提到 Xceed.Words.NET 或 Xceed.Document.NET。原因这两个 DLL 没有被复制到程序运行的输出目录。直接添加引用时如果没把“复制本地”设为 True或者发布时漏掉了这两个文件运行时程序集加载器在当前目录找不到它们就会抛异常。解决方法是选中解决方案资源管理器里的引用项打开属性面板把“复制本地”设为 True已经发布过的手动把两个 DLL 拷到 exe 同目录。另外如果项目内同时存在其他版本的同一个 DLL比如 NuGet 也引入了一份输出目录里会出现两个同名文件覆盖顺序混乱也会触发这个错。5.2 编译错误类型同时存在于这两个 DLL 中现象编译时报 CS0433提示某个类在 Xceed.Document.NET 和 Xceed.Words.NET 中同时定义类型不明确。原因很多 DocX 版本里两个 DLL 的依赖关系不是均匀的Xceed.Words.NET 内部引用了 Xceed.Document.NET 中的部分类型而项目代码同时把两个命名空间没有区分地 using 进来编译器无法确定你用的是哪个定义。解决方法是不要同时把两个 DLL 都加到引用列表里只添加 Xceed.Words.NET.dll因为它已经隐式依赖了另一个如果两个都得加那么代码里显式写全限定名比如 Xceed.Document.NET.Alignment。我实际处理时更倾向于把 Xceed.Document.NET.dll 从引用列表移除绝大多数场景下只留 Xceed.Words.NET 一个引用就够了。5.3 中文内容变成乱码或方块现象生成的 docx 在 Office 里打开中文显示为乱码或者方块字形。原因分两层一是代码里字符串本身编码不对把 C# 源文件存成了非 UTF-8 格式中文字符被破坏二是字体不匹配DocX 默认使用的字体名在系统里不存在Word 就用替代字体渲染。解决方法是先确认 C# 源文件保存为 UTF-8 with BOM再在代码里对包含中文的段落实显式设置中文字体比如 .Font(new Font(微软雅黑))或者在文档创建后通过 Styles 设置默认字体名。我经常遇到的是第二类尤其是服务器环境没有安装对应字体替换字体后显示就正常了。5.4 生成的 docx 打开时提示“文件已损坏是否修复”现象用 DocX 保存出来的文件在 Word 里打开时弹出修复提示点修复后内容基本完整但每次打开都提示。原因文档流没有正确关闭或者保存时文档对象内有未正确初始化的元素比如空表格、空图片引用导致 OOXML 结构不完整。解决方法是三步排查第一步确认所有插入的图片和表格都调用了对应的创建方法而不是直接 new第二步检查代码中 DocX 的 using 是不是完整覆盖了 Save 之后的所有逻辑第三步如果用了替换功能确认替换后的文本里没有异常字符。我自己遇到过的场景是在合并单元格后没有重新计算表格结构导致底层 XML 出现无效的网格范围定义。5.5 转 PDF 后排版与 Word 里不一致现象同一份 docx 文件用 LibreOffice 转出来的 PDF 和 Word 打开看到的效果有明显差异常见是行距变大、自动换行位置不同、嵌入图片位置偏移。原因LibreOffice 和 Word 对字体度量、段落间距、表格自动调整算法的理解不完全一致这是引擎级差异不是代码能完全消除的。解决方法是转 PDF 的目标环境装好和文档一致的中文字体宋体、黑体、微软雅黑都要覆盖另外在 docx 里尽量使用固定表格宽度而不是自动宽度段前段后间距用磅值固定不要依赖默认模板的样式继承。多语言混合文本的场景LibreOffice 对中文的标点压缩处理与 Word 差异明显能用半角标点就尽量别用全角。5.6 许可证相关弹窗或提示现象某些封装包替换 DLL 后运行出现授权提示弹窗或者初始化时抛 LicenseException。原因DocX 库在商业使用上带有授权约束部分版本会检查许可状态。解决方法是优先使用 Apache License 的开源分支自带的这份 DLL 如果来自开源发行版则没有该逻辑如果来自试用版那集成到产品中要评估授权合规。实际项目里最稳妥的是只把 DocX 用于内部工具和生成逻辑不把它的能力打包进商业产品的对外交付模块。遇到弹窗不要试图绕过合法合规的做法是确认库的许可证条款后再使用。6. 把 DocX 包进你的工具链模板锚点批量生成文档的一个顺手写法前几章解决了单文档怎么生成、怎么转 PDF、怎么排错最后一节把这些能力串成一个可复用的批量生成工具。思路是先用 Word 做一份带 {{变量}} 锚点的模板文件用 DocX 打开并批量替换锚点配合 JSON 数据源循环生成多份文档。这样做的好处是业务人员可以维护模板工程师只维护数据映射模板改版不用动代码。public static void BatchGenerate(string templatePath, ListDictionarystring, string dataList, string outDir) { for (int i 0; i dataList.Count; i) { string outPath Path.Combine(outDir, $report_{i 1}.docx); using (DocX doc DocX.Load(templatePath)) { foreach (var kv in dataList[i]) { doc.ReplaceText({{ kv.Key }}, kv.Value, ReplaceTextOptions.All); } doc.SaveAs(outPath); } } }这个方法的逻辑很直接DocX.Load 以模板文件为基底创建文档对象遍历数据字典逐项替换锚点所有 {{变量}} 全部替换完毕后另存为新文件。Run 到批处理时外层传进来一个 List每项代表一份独立文档的数据循环内完成生成。SaveAs 和 Create 的区别是前者从已有文档加载后者从空白创建两者返回的都是 DocX 对象后续 API 完全一致。ReplaceTextOptions.All 参数在这里是必须的因为同一个模板可能要在一个段落里多次出现同一个变量默认只替换第一处不传全替换的话数据会漏。使用这套工具时有几个习惯我从翻车经历里总结出来已经形成固定流程。第一模板锚点统一用大写加下划线命名比如 {{PROJECT_NAME}}、{{REPORT_DATE}}同时内置一份锚点清单给业务方防止他们随手敲一个没定义的变量导致替换静默失败。第二批量生成前先跑一条测试数据生成的文档打开检查一遍再批量执行尤其要留意纯文本之外的表格行数、图片位置这些 DocX 代码里写死的东西。第三输出目录按日期加批次建子目录生成完把 docx 原文件和 PDF 转换结果归档到一起方便追溯。从那以后我每次跑批量生成都会强制走一遍三件套先确认 DLL 版本和目标框架匹配再用最小 demo 验证当前环境能出文件最后拿真实模板只替换一条数据做排版验证。这套动作看起来繁琐但能拦截掉大部分“批量跑完发现全部要重做”的尴尬。希望这套思路帮你在文档自动化这条路上少踩几个坑。本文还有配套的精品资源点击获取