在C#项目开发中,按照固定模板生成Word文档是常见需求,比如批量生成员工合同、项目报告、录取通知书等文件,使用NPOI库可以在无Office依赖的环境下高效完成这个任务。

环境准备
首先需要安装NPOI相关的NuGet包,在Visual Studio的包管理器控制台执行以下命令即可完成安装:
Install-Package NPOI Install-Package NPOI.OOXML
这两个包分别提供了基础的NPOI功能和Office Open XML格式文档的支持,Word的.docx格式属于OOXML格式,因此需要同时安装这两个包。
制作Word模板
首先需要制作一个Word模板文件,在模板中需要替换的位置使用占位符标记,比如使用${占位符名称}的格式,例如:
- 员工姓名:
${EmployeeName} - 入职日期:
${EntryDate} - 岗位名称:
${Position}
模板制作完成后,将文件保存为.docx格式,放到项目的指定目录下,比如设置为始终复制到输出目录。
核心实现代码
下面是完整的按照模板生成Word文档的C#代码实现:
using NPOI.OpenXmlFormats.Wordprocessing;
using NPOI.XWPF.UserModel;
using System;
using System.Collections.Generic;
using System.IO;
namespace WordTemplateGenerator
{
public class WordGenerator
{
/// <summary>
/// 按照模板生成Word文档
/// </summary>
/// <param name="templatePath">模板文件路径</param>
/// <param name="outputPath">输出文件路径</param>
/// <param name="replaceData">替换数据字典,key为占位符名称,value为替换内容</param>
public static void GenerateWordByTemplate(string templatePath, string outputPath, Dictionary<string, string> replaceData)
{
// 读取模板文件
using (FileStream templateStream = new FileStream(templatePath, FileMode.Open, FileAccess.Read))
{
XWPFDocument doc = new XWPFDocument(templateStream);
// 替换段落中的占位符
foreach (XWPFParagraph paragraph in doc.Paragraphs)
{
ReplaceParagraphText(paragraph, replaceData);
}
// 替换表格中的占位符
foreach (XWPFTable table in doc.Tables)
{
foreach (XWPFTableRow row in table.Rows)
{
foreach (XWPFTableCell cell in row.GetTableCells())
{
foreach (XWPFParagraph cellParagraph in cell.Paragraphs)
{
ReplaceParagraphText(cellParagraph, replaceData);
}
}
}
}
// 保存生成的文档
using (FileStream outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
{
doc.Write(outputStream);
}
}
}
/// <summary>
/// 替换段落中的占位符
/// </summary>
private static void ReplaceParagraphText(XWPFParagraph paragraph, Dictionary<string, string> replaceData)
{
// 获取段落的所有文本片段
List<XWPFRun> runs = new List<XWPFRun>(paragraph.Runs);
if (runs.Count == 0) return;
// 拼接所有run的文本,得到完整段落文本
string fullText = string.Join("", runs.ConvertAll(run => run.Text ?? ""));
// 检查是否有需要替换的占位符
bool hasReplace = false;
foreach (var item in replaceData)
{
string placeholder = "${" + item.Key + "}";
if (fullText.Contains(placeholder))
{
fullText = fullText.Replace(placeholder, item.Value);
hasReplace = true;
}
}
// 如果有替换,清空原有run,重新设置文本
if (hasReplace)
{
for (int i = runs.Count - 1; i >= 0; i--)
{
paragraph.RemoveRun(i);
}
XWPFRun newRun = paragraph.CreateRun();
newRun.SetText(fullText);
// 可以复制原有第一个run的样式
if (runs.Count > 0)
{
newRun.FontFamily = runs[0].FontFamily;
newRun.FontSize = runs[0].FontSize;
newRun.IsBold = runs[0].IsBold;
newRun.IsItalic = runs[0].IsItalic;
}
}
}
}
}
调用示例
下面是调用上述方法的示例代码:
using System;
using System.Collections.Generic;
namespace WordTemplateGenerator
{
class Program
{
static void Main(string[] args)
{
// 模板路径和输出路径
string templatePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Template", "EmployeeContract.docx");
string outputPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Output", "张三_劳动合同.docx");
// 准备替换数据
Dictionary<string, string> replaceData = new Dictionary<string, string>
{
{ "EmployeeName", "张三" },
{ "EntryDate", "2024-05-20" },
{ "Position", "高级开发工程师" },
{ "Salary", "25000" }
};
try
{
// 生成文档
WordGenerator.GenerateWordByTemplate(templatePath, outputPath, replaceData);
Console.WriteLine("Word文档生成成功,路径:" + outputPath);
}
catch (Exception ex)
{
Console.WriteLine("生成文档失败:" + ex.Message);
}
}
}
}
注意事项
- 模板中的占位符不要拆分到多个
XWPFRun中,否则会导致替换失败,建议占位符作为完整的文本片段存在。 - 如果需要替换图片,需要额外处理图片的替换逻辑,上述代码仅支持文本占位符的替换。
- 生成的文档路径需要确保目录存在,否则会抛出文件不存在的异常,可以在生成前判断目录是否存在,不存在则创建。
- 如果需要处理复杂的格式,比如保留原有占位符的样式,需要更精细地处理
XWPFRun的样式复制逻辑。