在企業級應用中,動態生成 Word 文件是常見需求。本文深度解析兩大主流方案:`Microsoft.Office.Interop.Word` (需安裝 Office) 與 `OpenXML SDK` (無需 Office、效能優、伺服器適用),提供完整架構與生產級程式碼。
透過 COM 自動化操作 Word 應用程式,支援所有 Word 功能但需授權、啟動慢、不適合伺服器端大量生成。
using Microsoft.Office.Interop.Word;
using System.Runtime.InteropServices;
public class WordInteropGenerator : IDisposable {
private Application _app;
private Document _doc;
private bool _disposed;
public WordInteropGenerator(bool visible = false) {
_app = new Application {
Visible = visible,
DisplayAlerts = WdAlertLevel.wdAlertsNone
};
}
/// 從範本建立文件
public void CreateFromTemplate(string templatePath, object data) {
object missing = Type.Missing;
_doc = _app.Documents.Add(templatePath, ref missing, ref missing, ref missing);
BindData(data);
}
/// 建立空白文件
public void CreateBlank() {
object missing = Type.Missing;
_doc = _app.Documents.Add(ref missing, ref missing, ref missing, ref missing);
}
/// 資料繫結:書籤、合併列表、內容控制項
private void BindData(object data) {
var props = data.GetType().GetProperties();
foreach (var prop in props) {
string name = prop.Name;
object value = prop.GetValue(data);
// 1. 書籤繫結
if (_doc.Bookmarks.Exists(name)) {
_doc.Bookmarks[name].Range.Text = value?.ToString() ?? "";
}
// 2. 合併列表 (MailMerge)
// 需在範本設定合併欄位
}
// 3. 表格資料填充
FillTables(data);
}
private void FillTables(object data) {
// 實作表格填充邏輯
}
/// 儲存文件
public void SaveAs(string path, WdSaveFormat format = WdSaveFormat.wdFormatDocumentDefault) {
object fileName = path;
object fileFormat = format;
object missing = Type.Missing;
_doc.SaveAs2(ref fileName, ref fileFormat, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing);
}
/// 轉 PDF
public void ExportAsPdf(string path) {
object fileName = path;
object missing = Type.Missing;
_doc.ExportAsFixedFormat2(
OutputFileName: (string)fileName,
ExportFormat: WdExportFormat.wdExportFormatPDF,
OpenAfterExport: false,
OptimizeFor: WdExportOptimizeFor.wdExportOptimizeForPrint,
ExportRange: WdExportRange.wdExportAllDocument,
Item: WdExportItem.wdExportDocumentContent,
IncludeDocProps: true,
KeepIRM: true,
CreateBookmarks: WdExportCreateBookmarks.wdExportCreateWordBookmarks,
DocStructureTags: true,
BitmapMissingFonts: true,
UseISO19005_1: false
);
}
public void Dispose() {
if (!_disposed) {
try {
_doc?.Close(false);
_app?.Quit(false);
} catch { }
finally {
// 強制釋放 COM 物件
if (_doc != null) Marshal.ReleaseComObject(_doc);
if (_app != null) Marshal.ReleaseComObject(_app);
_doc = null;
_app = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
_disposed = true;
}
}
}
// 使用範例
public void GenerateReport() {
var data = new {
Title = "月度銷售報表",
Date = DateTime.Now.ToString("yyyy年MM月dd日"),
Department = "業務部",
Items = new[] {
new { Product = "產品A", Qty = 100, Amount = 50000 },
new { Product = "產品B", Qty = 200, Amount = 80000 }
}
};
using (var gen = new WordInteropGenerator()) {
gen.CreateFromTemplate(@"C:\Templates\ReportTemplate.dotx", data);
gen.SaveAs(@"C:\Output\Report.docx");
gen.ExportAsPdf(@"C:\Output\Report.pdf");
}
}
直接操作 `.docx` (OOXML) 封裝格式,純 .NET 程式碼、無 COM 開銷、執行緒安全、適合大量生成。
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System.IO;
public class WordOpenXmlGenerator {
/// 建立文件
public void CreateDocument(string outputPath, Action buildAction) {
using (var doc = WordprocessingDocument.Create(outputPath,
DocumentFormat.OpenXml.WordprocessingDocumentType.Document)) {
// 建立基本結構
MainDocumentPart mainPart = doc.AddMainDocumentPart();
mainPart.Document = new Document(new Body());
// 執行建構邏輯
buildAction(doc);
mainPart.Document.Save();
}
}
/// 從範本建立 (複製並修改)
public void CreateFromTemplate(string templatePath, string outputPath,
Action modifyAction) {
File.Copy(templatePath, outputPath, true);
using (var doc = WordprocessingDocument.Open(outputPath, true)) {
modifyAction(doc);
}
}
// === 輔助建構方法 ===
public Paragraph AddParagraph(Body body, string text, string styleId = null,
JustificationValues alignment = JustificationValues.Left) {
var p = new Paragraph();
if (styleId != null) p.ParagraphProperties = new ParagraphProperties(
new ParagraphStyleId { Val = styleId });
p.ParagraphProperties = p.ParagraphProperties ?? new ParagraphProperties();
p.ParagraphProperties.Justification = new Justification { Val = alignment };
p.Append(new Run(new Text(text) { Space = SpaceProcessingModeValues.Preserve }));
body.Append(p);
return p;
}
public Table AddTable(Body body, int rows, int cols, string[] headers = null) {
var table = new Table();
// 表格屬性:邊框、寬度
table.Append(new TableProperties(
new TableBorders(
new TopBorder { Val = BorderValues.Single, Size = 4 },
new BottomBorder { Val = BorderValues.Single, Size = 4 },
new LeftBorder { Val = BorderValues.Single, Size = 4 },
new RightBorder { Val = BorderValues.Single, Size = 4 },
new InsideHorizontalBorder { Val = BorderValues.Single, Size = 4 },
new InsideVerticalBorder { Val = BorderValues.Single, Size = 4 }
),
new TableWidth { Width = "5000", Type = TableWidthUnitValues.Pct }
));
// 標題列
if (headers != null) {
var headerRow = new TableRow();
foreach (var h in headers) {
headerRow.Append(CreateCell(h, true));
}
table.Append(headerRow);
}
// 資料列
for (int i = 0; i < rows; i++) {
var row = new TableRow();
for (int j = 0; j < cols; j++) {
row.Append(CreateCell($"R{i}C{j}"));
}
table.Append(row);
}
body.Append(table);
return table;
}
private TableCell CreateCell(string text, bool isHeader = false) {
var cell = new TableCell();
cell.Append(new TableCellProperties(
new TableCellVerticalAlignment { Val = TableVerticalAlignmentValues.Center }
));
var p = new Paragraph(
new Run(new RunProperties(
isHeader ? new Bold() : null,
new RunFonts { Ascii = "微軟正黑體" }
),
new Text(text) { Space = SpaceProcessingModeValues.Preserve }
));
cell.Append(p);
return cell;
}
public void FillTableData(Table table, IEnumerable
| 維度 | Office Interop | OpenXML SDK |
|---|---|---|
| Office 安裝需求 | 必須安裝完整 Office | 完全不需要 |
| 授權成本 | 每台裝置需授權 | 免費 (MIT 授權) |
| 執行環境 | 桌面/終端機 (非服務帳號) | 任何環境 (服務、容器、Linux) |
| 啟動速度 | 慢 (數秒啟動 Word) | 極快 (毫秒級) |
| 記憶體佔用 | 高 (Word 進程 ~100MB+) | 低 (純 managed) |
| 並發能力 | 差 (單進程序列化) | 優 (執行緒安全、可大規模並行) |
| 功能完整度 | 100% Word 功能 | 核心功能完整、進階排版需手工 XML |
| PDF 匯出 | 原生完美 | 需第三方 (如 LibreOffice、商業元件) |
| 範本支援 | 書籤、合併列表、內容控制項 | 需程式碼對應、或使用文件組件 |
| 除錯難度 | COM 例外難追蹤 | 標準 .NET 例外、可單元測試 |
public interface IWordGenerator : IDisposable {
void CreateFromTemplate(string templatePath, object data);
void SaveAs(string path);
void ExportAsPdf(string path); // OpenXML 可能拋出 NotSupportedException
}
public class WordGeneratorFactory {
public static IWordGenerator Create(WordEngine engine = WordEngine.Auto) {
if (engine == WordEngine.Auto) {
// 偵測環境:伺服器/容器 -> OpenXML,桌面且裝 Office -> Interop
bool isServer = Environment.UserInteractive == false ||
System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
bool hasOffice = Type.GetTypeFromProgID("Word.Application") != null;
engine = (isServer || !hasOffice) ? WordEngine.OpenXml : WordEngine.Interop;
}
return engine switch {
WordEngine.Interop => new WordInteropGenerator(),
WordEngine.OpenXml => new WordOpenXmlAdapter(), // 適配器模式
_ => throw new ArgumentException("不支援的引擎")
};
}
}
public enum WordEngine { Auto, Interop, OpenXml }
選擇正確的 Word 生成引擎,配合範本驅動架構與策略模式,能滿足從桌面報表到雲端大量文件生成的各種企業級需求。