ASP.NET 將 Word 文件(.doc/.docx)轉換為 PDF 主要有三條技術路線:
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| Microsoft Office Interop | 零成本、格式還原度最高 | 需伺服器安裝 Office、非執行緒安全、Com 元件權限複雜、效能差 | 內部低併發、格式極度複雜、預算為零 |
| 商業庫 | 無 Office 相依、高效能、支援 .NET Core/5+、雲原生 | 授權費用高 | 企業級生產環境、高併發、跨平台 |
| 開源庫 | 免費、無 Office 相依、跨平台 | 複雜排版支援有限、維護週期不確定 | 預算受限、簡單文件、.NET Core 專案 |
dcomcnfg → 元件服務 → Microsoft Word 97-2003 文件 → 身分識別 選「互動使用者」或指定服務帳號。Microsoft.Office.Interop.Word(NuGet 同名套件)。using Microsoft.Office.Interop.Word;
using System;
using System.Runtime.InteropServices;
public static class WordToPdfConverter
{
public static bool Convert(string sourcePath, string targetPath)
{
Application app = null;
Document doc = null;
object missing = Type.Missing;
try
{
app = new Application { Visible = false, DisplayAlerts = WdAlertLevel.wdAlertsNone };
doc = app.Documents.Open(sourcePath, ReadOnly: true, Visible: false);
doc.ExportAsFixedFormat(
targetPath,
WdExportFormat.wdExportFormatPDF,
OpenAfterExport: false,
OptimizeFor: WdExportOptimizeFor.wdExportOptimizeForPrint,
Range: WdExportRange.wdExportAllDocument,
Item: WdExportItem.wdExportDocumentContent,
IncludeDocProps: true,
KeepIRM: true,
CreateBookmarks: WdExportCreateBookmarks.wdExportCreateWordBookmarks,
DocStructureTags: true,
BitmapMissingFonts: true,
UseISO19005_1: false);
return true;
}
catch (Exception ex)
{
// 記錄 Log
return false;
}
finally
{
// 嚴格釋放 COM 物件,避免 Word 殭屍進程
if (doc != null)
{
doc.Close(SaveChanges: false);
Marshal.ReleaseComObject(doc);
}
if (app != null)
{
app.Quit();
Marshal.ReleaseComObject(app);
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
Application 實例,避免頻繁啟動/關閉 Word 進程(需自行實作執行緒安全池)。WINWORD.EXE 記憶體超標或掛起時自動回收進程池。// NuGet: FreeSpire.Doc
using Spire.Doc;
public static bool ConvertWithFreeSpire(string source, string target)
{
try
{
var doc = new Document();
doc.LoadFromFile(source);
doc.SaveToFile(target, FileFormat.PDF);
return true;
}
catch { return false; }
}
伺服器安裝 LibreOffice,呼叫 CLI:
public static bool ConvertWithLibreOffice(string source, string target)
{
var psi = new ProcessStartInfo
{
FileName = "soffice",
Arguments = $"--headless --convert-to pdf --outdir \"{Path.GetDirectoryName(target)}\" \"{source}\"",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
using var proc = Process.Start(psi);
proc.WaitForExit(120_000); // 2 分鐘超時
return proc.ExitCode == 0 && File.Exists(target);
}
優點:完美支援複雜排版、跨平台、無授權費。
缺點:啟動較慢、需安裝完整 LibreOffice、非執行緒安全(建議同樣採用訊息佇列隔離)。
// Web API 控制器
[HttpPost("convert")]
public async Task Convert(IFormFile file)
{
var jobId = Guid.NewGuid();
var inputPath = $"/storage/input/{jobId}{Path.GetExtension(file.FileName)}";
var outputPath = $"/storage/output/{jobId}.pdf";
using (var fs = System.IO.File.Create(inputPath))
await file.CopyToAsync(fs);
// 發送轉換任務至佇列
await _queue.SendMessageAsync(new ConvertJob { JobId = jobId, InputPath = inputPath, OutputPath = outputPath });
return Accepted(new { JobId = jobId, StatusUrl = Url.Action(nameof(Status), new { jobId }) });
}
// 背景 Worker(獨立進程/容器)
public class ConvertWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
var msg = await _queue.ReceiveMessageAsync(ct);
if (msg == null) continue;
var job = JsonSerializer.Deserialize(msg.Body);
bool success = WordToPdfConverter.Convert(job.InputPath, job.OutputPath); // 或 LibreOffice
await _resultStore.SetAsync(job.JobId, new { Success = success, FileUrl = success ? $"/download/{job.JobId}.pdf" : null });
_queue.DeleteMessage(msg);
}
}
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 8000401A / 80070005 COM 權限錯誤 | DCOM 身分識別設定錯誤、互動式登入缺失 | 設定 DCOM 為特定服務帳號,並授予「本機啟動/啟動」權限 |
| 轉換卡住、Word 進程殭屍 | 未正確釋放 COM 物件、例外導致 Quit 未執行 | finally 區塊強制 Marshal.ReleaseComObject + GC.Collect;改用隔離服務 |
| 字型缺失導致亂碼/版面錯位 | 伺服器缺少文件使用的字型 | 安裝對應字型;或轉換前嵌入字型(Interop 不支援,需商業庫) |
| .NET Core / Linux 環境無法使用 Interop | Interop 僅支援 Windows + Office | 改用 LibreOffice Headless 或商業跨平台庫 |