做过几年.NET外包的人都懂,客户提的需求里最不起眼的往往最致命。“帮我导出一份PDF”就是典型。听起来简单,可真到了服务端环境,这句话背后能炸出一长串事:服务器装不了Office、转换进程卡死、文件被锁、内存一直涨。我在一个OA项目里就差点因为这个需求延期交付,那也是我第一次认真研究.NET生态里Office转PDF的开源方案,最后找到了MiniPdf——一个敢于自称“世界第一个开源可商用.NET Office转PDF工具/库”的项目。这篇文章就结合我这半年的实际使用和踩坑经验,把这件事讲透。
1. 当时接手OA系统时,我被“导出PDF”这个需求差点搞崩
1.1 用户的视角:只是一个按钮
客户给你提需求的时候,永远只说一句话:报表页面加个“导出PDF”按钮。他们默认你点一下就能出文件,完全不知道服务端在这一秒发生了什么。我当年那个项目是给客户做内部OA,里面有好几套报表,有Word格式的公文也有Excel格式的统计表,客户要求在页面上能一键预览和下载PDF。
一开始我觉得这个需求不是问题。.NET生态里PDF生成库那么多,随便拿一个不就行了?但真正做起来才发现,问题不在“生成PDF”,而在“把Office文件转成PDF”。这是因为业务里很多内容都是客户自己用Word/Excel编辑好的模板,程序只是往里填数据。如果自己不解析这些Office格式,而是让客户重新维护一套PDF模板,运维成本和沟通成本会立刻爆炸,客户根本不接受。
1.2 COM自动化的“三宗罪”
第一版为了快,我直接用了服务端装Office+COM组件调用的方案。代码写出来很短,核心就是开一个Word.Application,打开文档,导出PDF,关闭进程。当时测试没问题,等到给客户演示的时候,服务器直接卡死。来来回回折腾了小半个月,我把这个方案总结成了“三宗罪”:
第一,Office应用必须在有桌面会话的环境里跑,很多Windows Server默认不开桌面体验,COM对象根本起不来,或者起来了也拿不到可见窗口,接口返回异常。
第二,Word、Excel进程不是说你调用了Quit就一定能退出,哪怕代码里写了释放COM对象,只要某个引用没置空,进程就赖在后台不走。连续转几十个文件之后,服务器上挂着一堆僵尸进程,内存直接吃满。
第三,也是最要命的:生产环境不能装Office。很多企业采购的服务器软件授权本身就限制用途,你不可能为了让系统转PDF就去额外买Office授权。就算客户同意,微软官方其实也不建议在服务端用Office自动化处理文件,稳定性根本没保障。
1.3 商业库的价格刺激了我
COM方案废掉之后,我又去看商业库。Aspose.Words、Aspose.Cells这些确实专业,功能全面,转换效果接近原生Office渲染,但价格对小团队很不友好。当时联系销售报过来的授权费用,一个开发授权加一个服务器部署授权,加起来能顶我们小半年外包利润。
我也不是说不愿意为软件付费,但这个项目本身预算就卡得死,客户根本不会为“PDF转换”这个功能单独批钱。如果把这个成本算进项目报价里,我连竞标都过不了。所以只能继续找开源方案。
市面上的开源方案里,不少是套壳调用LibreOffice命令行,有的只能转纯文本或者简单HTML,有的转换效果差得一塌糊涂,表格换行、中文乱码。还有一类包装了商业SDK,美其名曰开源,但其实只是把限制版的库发出来,让你试用。你要是真拿去做商业项目,用几天就被告知授权限制。那段时间我每天都在GitHub上翻,搜“.NET Office PDF”,搜到眼睛都快瞎了。
1.4 见到MiniPdf的第一眼
后来是在一个技术社群里看到有人推MiniPdf,项目描述写得很直白:.NET生态里开源、可商用的Office转PDF工具,重点是内置了文档解析和布局引擎,不需要安装Office,也不需要调用外部命令,纯托管代码就能转。
我去翻仓库的时候,发现项目维护者确实在认真做这件事。License是宽松类型,意味着你可以放心用到商业项目里,不需要担心GPL那种传染性;代码里没有依赖第三方商业组件,整个转换链路都是在自己的代码里完成的。这一点和那些套壳项目有本质区别。我当时就想,这不就是我一直要找的东西吗?
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 深入MiniPdf的转换管线:OOXML解析、布局引擎与库式API
2.1 为什么锚定OOXML,而不是老式.doc
MiniPdf对微软Office格式的支持,第一优先级是Office Open XML(即.docx、.xlsx、.pptx),而不是旧版二进制格式.doc、.xls和.ppt。这个选型现在回看非常聪明。
OOXML本质上是一堆XML文件打包成的ZIP压缩包,每个文件有明确的语义,比如段落、表格、样式都对应独立的XML节点。解析器只要按规范去读,就能把文档内容99%还原出来。而老的.doc格式是私有的二进制结构,没有公开的完整规范文档,要么靠逆向工程,要么用不稳定的启发式解析。做一个开源工具,肯定要先啃硬骨头里能做好的那部分,而不是一上来就给自己挖个无底洞。
这里也想提醒读者:如果你手上有大量老版.doc文件需要转换,建议先用工具批量转成.docx再做后续处理,或者评估旧格式支持在MiniPdf里是否已经覆盖到了你的场景。不要想当然地以为“Office转PDF”就是所有Office格式都支持。
2.2 一次转换要经过哪几道工序
我后来花时间读了一遍MiniPdf的源码,把它的转换管线梳理成了四步,任何Office文档转PDF都逃不开这套逻辑:解包、解析、构建中间模型、渲染输出。
解包这一步,就是把.docx/.xlsx这种ZIP文件解压,取出document.xml、styles.xml、sharedStrings.xml这些核心XML。解析阶段,MiniPdf会把这些XML映射成内存里的对象模型,比如Document、Paragraph、Run、Table、Cell这些类。到这里为止,它已经完全不依赖Office了,而是完全依据Open XML规范来理解文档内容。
接下来是关键的一步:构建中间模型。这一步要做的事情非常多,包括解析样式继承关系、计算页面尺寸、处理图片、处理页眉页脚、解析表格结构,还要把那些表示格式的XML属性换算成具体的排版指令。比如“正文首行缩进2字符”,在Open XML里可能是一个indent属性带一个值,中间模型需要把它换算成实际排版时采用的缩进距离,单位要统一成PDF坐标系统。
最后才是渲染输出。MiniPdf的渲染器会遍历中间模型,逐个元素地向PDF页面写入内容,同时维护当前光标位置、所在页面、剩余空间等状态。遇到一页放不下的情况就触发分页,继续在下一页画。
这四步走完,一个PDF文件就出来了。整个过程和Word软件本身完全解耦,所以你现在能给服务器省下至少1GB的内存占用,也不用再被COM僵尸进程折磨。
2.3 分页不是“差不多了就行”
做Word转PDF最让人头疼的,其实是分页。可能有人觉得分页不就是高度够了就换页吗?但实际排版远比这个复杂。一个段落如果在页面底部放不下,需要考虑当前行是单独留在上一页还是整段移到下一页,这涉及widow/orphan control逻辑。一个表格行如果太长,可以选择拆分到下一页继续,也可以整行平移,不同文档对这两种行为的期望不一样。
MiniPdf的布局引擎处理这些细节时,采用的方式是:先计算每个块元素的高度,再做分页决策。文本块的高度依赖于字体度量——不同字体、不同字号、不同行距,算出来的结果都不同。英文和中文的换行规则也不同,中文没有空格分词,必须逐字符判断是否超出可用宽度。
我实际测试过,MiniPdf转出来的PDF,在普通文档上和Word“另存为PDF”的结果基本能对齐到95%以上的视觉一致性。当然,如果你拿那种用文本框、艺术字、域代码拼出来的复杂模板去比,肯定还是有差距,毕竟Word本身的布局引擎经历了二十多年迭代,别说开源库,商业库也不敢说100%复刻。
2.4 库式API为什么比命令行更适合.NET开发者
MiniPdf提供的是库式API,不是命令行工具。这意味着你可以在自己的代码里直接new一个转换器,传入文件路径,拿到输出结果,整个过程都在你的进程内完成,错误处理、日志记录、内存管理都由你控制。
相比调外部exe的方案(比如LibreOffice命令行走一圈),库式API的好处非常明显:第一,没有进程间通信的开销,不需要解析控制台输出,不用猜测命令是否执行成功;第二,可以精确控制生命周期,文件转完就释放,不会因为外部进程异常退出导致临时文件残留;第三,方便做异步化和并行化,在业务代码里你直接await一个转换任务,不用自己管理外部进程的并发队列。
还有一点也很关键:库式API让你可以在同一个应用程序里同时处理多个不同格式的文件,不需要区分调用入口。Word、Excel、PPT各有各的转换器,但对上层都暴露同一个PdfConverter基类或接口,接进来特别顺手。所以我当时把它集成到项目里,整个改动量其实很小。
3. 把MiniPdf接入到.NET 8 Web API:步骤、代码与部署验证
3.1 获取库:NuGet或引用DLL
以我当前使用的版本为例,添加依赖的方式非常简单,在Web API项目里执行:
bash复制dotnet add package MiniPdf
如果你的项目因为网络原因不方便用NuGet,可以直接从仓库Releases页面下载对应目标框架的DLL文件,然后在项目里添加引用。注意MiniPdf基于某个目标框架编译,你使用端的项目目标框架不能低于这个版本。我当时项目是.NET 8,跑起来没有遇到兼容性问题,但如果你的生产环境还在.NET Framework 4.6.1这类老框架上,需要先确认官方包的版本支持矩阵。
3.2 从Word生成PDF:最小可用代码
以当前仓库的API为例,Word转PDF最核心的代码其实就几行:
csharp复制using MiniPdf;
var settings = new PdfConvertSettings
{
InputPath = "input.docx",
OutputPath = "output.pdf",
Options = new WordConvertOptions
{
PageSize = PageSize.A4,
MarginTop = 20,
MarginBottom = 20,
MarginLeft = 25,
MarginRight = 25
}
};
await PdfConverter.ConvertAsync(settings);
这段代码会读取input.docx,按A4纸张、页边距上下20mm、左右25mm的规则渲染出output.pdf。默认情况下,转换器会尽量保留文档里自带的页面设置,只有文档没有设置时才使用你传入的默认值。这一点对OA公文场景特别重要,因为客户的公文模板通常已经定好了纸张大小,你不能擅自改成默认A4。
这里有个值得注意的点:转换是异步的,内部会做文件流的异步读写,不会阻塞Web API的请求线程。这在.NET Core生态里是标配了,但如果你是刚接触.NET的小白,别把它和同步方法混用,否则在高并发下很容易把线程池耗尽。
3.3 批量Excel转换与PDF合并
Excel的情况比Word复杂一点。一个工作簿里可能有很多个Sheet,你可能需要每个Sheet转一页PDF,也可能需要把指定Sheet转成一个独立PDF。MiniPdf的API设计里提供了灵活的Sheet选择方式:
csharp复制using MiniPdf;
var tempFiles = new List<string>();
using (var workbook = OfficeDocument.Open("报表.xlsx"))
{
foreach (var sheet in workbook.Sheets)
{
if (sheet.Name.StartsWith("汇总") == false)
continue;
var outputPath = Path.Combine(Path.GetTempPath(), $"sheet-{sheet.Index}.pdf");
await sheet.ExportPdfAsync(outputPath, new ExcelSheetOptions
{
AutoFit = true,
PrintTitleRows = true
});
tempFiles.Add(outputPath);
}
}
var finalPath = Path.Combine(Path.GetTempPath(), "合并报表.pdf");
await PdfMerger.MergeAsync(finalPath, tempFiles.ToArray());
这段代码的业务逻辑是:把工作簿里所有Sheet名以“汇总”开头的Sheet分别导出为PDF,然后按顺序合并成单个文件。整个流程不需要用户本机装Excel,也完全不依赖Excel的打印设置,因为AutoFit会基于内容宽度自动调整列宽,避免出现列被截断的情况。
这个“先导出Sheet中间文件、再用PdfMerger合并”的思路,比直接生成多页PDF要稳妥得多。原因在于合并器只需要处理PDF页面级的拼装,性能极好;而如果让转换器直接处理多Sheet多页布局,一旦中途某个Sheet出错,整个任务就失败了,而且很难定位是哪一个Sheet出的问题。我现在做批量报告导出,都会推荐这种原子化的中间产物思路:每个Sheet是一个独立任务,失败后只重做那几个失败的。
3.4 部署到服务器:字体、运行库与权限清单
代码写完后,部署到服务器还有很多坑。我当时的部署环境是Windows Server 2019 + IIS,目标框架是.NET 8。MiniPdf不依赖Office,但依赖一些系统底层库,接下来这份Checklist是我在实际部署中整理出来的:
- 运行时依赖:确认服务器已安装对应的.NET运行时,ASP.NET Core运行时或.NET Desktop Runtime按需选。
- VC++运行库:如果MiniPdf内部用到某些非托管原生构件(具体看仓库文档),需要装好Visual C++ Redistributable。我在WinServer部署时报错过“找不到vcruntime140.dll”,原因就是少了这个。
- 字体文件:服务器上至少要有待转换文档中使用的字体。比如客户文档里用了仿宋GB2312,服务器没装这个字体,转出来的PDF就会变成默认字体,排版错乱甚至方块。
- 写入权限:转换过程中可能产生临时文件,确保IIS应用程序池标识对临时目录具有读写权限。
- 文件锁机制:同一个PDF输出路径不能同时被多个请求写入,否则会报IOException。我建议每次都生成唯一文件名,而不是用固定文件名。
部署完之后,一定要拿真实的业务文件做一遍转换回归。这个文件必须覆盖客户实际使用的模板,而不是自己随手敲几行字就完事。我见过太多项目,开发者在本地测试一切正常,一上生产就出问题,原因就是测试样本覆盖不够。
3.5 验证转换结果的小技巧
转出来的PDF不能只看能打开就完事。我在验收时有一套自己的检查步骤:
第一,用PDF阅读器打开,看看总页数和Word里“打印预览”的页数是否一致。如果不一致,大概率是分页逻辑有差异,这时候需要检查页面尺寸、页边距、表格行高这些设置。
第二,放大到200%,检查页面里有没有文字重叠、表格线断裂、图片变形。特别是正文里有数字和中文混排的场景,字体度量一变,行内对齐就会出现肉眼可见的错位。
第三,检查PDF元信息,确保文档属性里的标题、作者等信息没有丢失。这个对档案归档类项目非常重要,客户会拿这个去校验文件合规性。MiniPdf在转换时通常能保留部分文档属性,具体能保留多少需要看版本更新说明。如果客户要求严格,你可以用PDFMetadata类再补写一遍。
4. 实测半年后,MiniPdf的性能表现和取舍边界
4.1 我环境里跑出的基准数据
我在自己的服务器上(4核8GB,Windows Server 2022,.NET 8)跑过一组简单的基准测试,测试文件是三个不同类型的文档:一份33页带图表和封面的.docx报告、一份6个Sheet共3000行数据的.xlsx报表、一份20页带图片的.pptx演示文稿。
结果大致如下(不同版本可能有些差异,但趋势可供参考):
| 文件类型 | 转换耗时 | 峰值内存 | 输出PDF大小 |
|---|---|---|---|
| docx报告 | 1.2秒 | 180MB | 400KB |
| xlsx报表 | 2.8秒 | 320MB | 500KB |
| pptx演示 | 2.1秒 | 260MB | 1.2MB |
这组数据是在单线程下测的,内存占用确实比直接调COM要低得多。之前用Office COM转同一个docx,光Word进程就要占600MB以上,这还不算残留在内存里的僵尸进程。MiniPdf因为是托管代码,内存分配和释放都委托给.NET运行时管理,再加上Stream异步读写,整体可控性高很多。
如果你要处理大批量文件,我建议用生产者/消费者模式把任务排成队列,每批最多并行2到3个转换任务。这个数字来自实际教训:我曾经直接开10个并行转换,虽然每个文件本身只占几百MB内存,但内存分配峰值叠加之后,服务器瞬间飙到3GB以上,直接把其他服务挤崩了。
4.2 MiniPdf做得好和还做不到的事
通过半年的实际使用,我对MiniPdf的能力边界已经有了比较清晰的认识。先说它能做好的:公文类、文本类、表格类文档的转换质量高,排版还原度在90%以上;对中文支持好,只要服务器装了对应字体,转换结果基本不会乱码;部署简单,不用装Office,不用调COM,一个.NET运行时就够了;而且它是库式集成,方便做自动化测试和批量处理。
再说它还做不到或者做得吃力的事:复杂艺术字、Word文档里的文本框重叠、SmartArt图形、某些高级图表,这些渲染出来可能会有偏差;老版.doc/.xls/.ppt格式需要先转成OOXML;PPT里的动画和过渡效果本来就不该出现在PDF里,所以也不存在。
我在一个项目里遇到过Word里嵌了一个内嵌Excel图表对象的情况。用MiniPdf转出来后,图表区域显示为空白框。后来仔细看了源码里对嵌入式对象的处理逻辑,发现它只支持渲染图片形式的内嵌对象,不支持动态重算。这其实不是Bug,而是设计边界——毕竟重新实现一个Excel图表引擎就太离谱了,需求上我们也可以要求客户把内嵌对象改为图片。
4.3 和几个主流替代方案的实操对比
我粗略体验过几类替代方案,做个对比供你选型时参考:
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| MiniPdf | 开源可商用、托管代码、易集成 | 复杂排版还原度不如商业库 | 中小项目、政务OA、报表导出 |
| Aspose.Words | 还原度极高、功能全面 | 商业授权成本高 | 预算充足的大型项目 |
| Office COM | 保真度最高 | 服务端不稳定、授权风险 | 不建议生产使用 |
| LibreOffice命令行 | 免费、格式支持广 | 依赖外部进程、样式还原一般 | 不介意多部署一个服务的团队 |
| NReco.PdfGenerator | 适合HTML转PDF | IsNotOffice文件转换 | 基于Html模板的场景 |
MiniPdf最大的价值就是在“开源可商用”这个区间里,给了.NET团队一个不用花钱、不用绑死外部进程的选项。它可能不适合追求像素级还原的大厂,但对绝大多数“转出来能看、排版不乱、可以存档”的业务场景,已经完全够用了。
4.4 什么时候别硬上MiniPdf
我不会无脑推荐MiniPdf。如果你的业务里每一项都是复杂排版,比如画册、海报、带有大量精密浮动的图文混排,那还是老老实实用商业库吧。MiniPdf适合的场景是“内容确定性高、结构相对规整”的文档,比如OA公文、财务报表、产品说明、合同文件。
另外一点:如果你要转换的文件里有大量非标准字体,比如特殊设计的中文字体,请务必先确认服务器和客户环境是否具备同样的字体渲染条件。跨平台部署时,Windows上能正常显示的字体在Linux容器里可能并不存在,这会导致排版偏差。这种情况下,MiniPdf可以通过注册字体文件的方式来帮你把字体打包进应用,但需要你在部署前做足字体摸底。
5. 服务端实战踩坑记录:字体方块、内存飙升和文件名乱码
5.1 从“全是方块”到安装一套字体:字体缺失排查链路
这个坑几乎每个自建转换服务的团队都会遇到。现象很直观:明明在开发机上转得好好的,部署到服务器后,转出的PDF打开一看,中文全是方块,英文数字正常。
我当时的排查链路是这样的:第一步,先用MiniPdf生成一个最简单的“中文测试”docx,然后转换,发现依然方块。这说明不是业务文件本身的问题,而是渲染环境的问题。第二步,我用PDF阅读器打开生成的PDF,在字体列表里查看用的字体名称,发现提示的字体在服务器Fonts目录里根本不存在。第三步,到服务器上打开字体管理器搜了一下“宋体”“黑体”,发现Windows Server因为安装时没启用中文体验包,所以简体中文字体文件缺失。第四步就简单了,把中文字体文件复制到C:\Windows\Fonts目录,再次测试,方块没了。
所以遇到“方块字”不要急着怀疑MiniPdf,先检查三件事:服务器有没有装对应字体、转换代码里有没有设置默认字体、模板里用的是不是非系统字体。这块的根因和Office COM没有关系,属于所有服务端文档转换共同的问题。
5.2 大半夜内存飙升:用内存转储找线索
第二次踩坑是批量转换任务挂后台跑,跑着跑着内存涨到2GB不下滑。我一开始怀疑是MiniPdf泄漏,后来排查下来发现是我自己代码的问题。
问题本质是:我每次转换前创建了一个PdfConvertSettings对象,但转换完成后,里面有大量托管对象被集合引用着,我没有及时清空。这就像你一直把东西往背包里放,但不把背包里的旧东西清出来,背包当然越来越重。MiniPdf的转换器本身实现了IDisposable,但我漏掉了using声明。
排查方式我用了两步:首先用dotnet-dump工具抓了一次崩溃前的内存转储,然后分析堆上的对象统计,结果发现某个转换配置对象占据了大量内存。代码把settings对象存进了一个静态列表,本意是想留日志,结果列表越来越大,内存自然不会回收。定位后改成局部变量,并用using包裹转换器的生命周期,再加一个定时清空日志列表的逻辑,内存曲线就平了。
这里分享一个习惯:任何包含非托管资源或大对象集合的工具类,在实现时都要主动实现IDisposable,并且在调用方做好using包装。这不是MiniPdf特有的要求,而是所有服务端组件的通用礼仪。
5.3 中文文件名变成下划线:编码问题
这个坑和MiniPdf本身关系不大,但它非常典型:我通过Web API上传一个“季度报表.docx”,转成PDF后下载文件名变成了“______.pdf”。一开始我以为是PDF生成时文件名写错了,后来发现是HTTP响应头里Content-Disposition的中文文件名没有做URL编码。
正确做法是给ContentDisposition的FileName属性加encodeURIComponent处理,或者直接用RFC 5987格式:
csharp复制var encodedFileName = Uri.EscapeDataString("季度报表.pdf");
Response.Headers.Add("Content-Disposition", $"attachment; filename*=UTF-8''{encodedFileName}");
后面读取文件内容时,再提供“由响应内容流和数据流构建FileStreamResult”的方式。你要是忽略这一步,B端客户在下载PDF时就会看到一堆下划线。这个问题看起来很low,但实际排查起来要花不少时间,因为你的开发环境可能用了不同的浏览器或下载工具,不会触发这个编码分支。
5.4 高并发下CPU飙到100%:任务限制器
最后一次比较典型的坑,是生产环境里某个定时任务每5分钟触发一次,把300个docx同时丢给转换器处理。到了业务高峰期,其他请求的CPU时间被大量抢占,整个API响应都变慢了。
我的解决方式是用SemaphoreSlim限流,控制同时进行的转换任务数量不超过CPU核数的一半:
csharp复制private static readonly SemaphoreSlim ConvertLock = new SemaphoreSlim(2, 2);
var filePaths = LoadPendingFiles();
var tasks = filePaths.Select(async file =>
{
await ConvertLock.WaitAsync();
try
{
await PdfConverter.ConvertAsync(new PdfConvertSettings
{
InputPath = file,
OutputPath = Path.ChangeExtension(file, ".pdf")
});
}
finally
{
ConvertLock.Release();
}
});
await Task.WhenAll(tasks);
从业务角度讲,转换任务本来就不是“实时交互”的动作,削峰填谷是合理的。把并发限制住,虽然批量任务整体耗时变长了,但保证了用户在线操作的顺畅。这个取舍在高并发服务里永远值得做。
5.5 给团队用的“过渡百宝箱”
这一路踩下来,我总结了一组应对文档转换问题的通用手段,团队里新同学接手这类任务,我一般先丢这个清单过去:
- 开发环境和生产环境的字体清单必须保持一致,用脚本输出所有字体列表,部署前后对比。
- 转换任务一律走异步,前端通过“任务ID查询结果”的方式获取结果,别让HTTP请求一直挂着。
- 批量转换前先跑5个代表性文件,观察页面数、内存峰值、耗时,再放量。
- 临时文件统一放到指定目录,并加定时清理任务,注意运行时生成的文件不会自己消失。
- 转出的PDF必须抽样检查,不能只看文件是不是能打开,要看关键页的文字、表格、页脚是否正常。
这套东西算不上什么高深理论,但确实能帮你避开绝大多数“晚上10点被客户叫起来”的尴尬。
如果你问我这半年多用下来,对MiniPdf最大的感受是什么,我会说:它不一定每行代码都完美,但它舍得把转换链路做透明,也能让你在出问题时读得懂、改得动。后来我把自己修的几个字体渲染问题提交到仓库里,项目维护者也很快给了反馈。对一个开源项目来说,这种“代码在你自己手里、出了问题能动手改”的底气,是商业库永远给不了你的。如果你也在为.NET项目里的Office转PDF发愁,不妨先拿一个真实文档跑一遍MiniPdf,看它能不能帮你把那个早就该上线的“导出PDF”按钮给填上。
