You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core API动态PDF生成器开发:PDF/docx占位符替换需求

动态PDF生成方案(.NET Core API + Docker)

我之前刚好做过类似的动态PDF生成服务,结合你的需求,给你梳理一套落地可行的方案,分步骤来:

一、核心思路拆解

你的需求本质是先制作可复用模板(替换固定内容为占位符),再基于模板填充动态数据生成最终PDF。这里分两种场景处理,优先推荐第一种,更稳定易维护:

1. 优先处理DocX模板(低复杂度高兼容性)

直接修改PDF的文本替换容易出现格式错乱、字体偏移的问题,建议先把DocX转换成带占位符的模板,再填充数据转PDF,流程:

  • 读取原始DocX文件 → 替换固定内容(如John Doe)为统一占位符(如#NAME_PLACEHOLDER)→ 保存为模板文件
  • 后续生成PDF时,读取模板DocX → 用Dictionary<string, string>替换所有占位符 → 转换为PDF

2. 直接处理PDF模板(适合已有PDF模板的场景)

如果必须基于现有PDF修改,需要用PDF处理库定位文本位置并替换,注意这种方式对复杂格式的PDF兼容性一般。


二、具体实现代码

场景1:DocX模板转PDF

依赖库安装

在你的.NET Core项目中安装NuGet包:

Install-Package DocX
Install-Package DinkToPdf

DinkToPdf基于wkhtmltopdf,适配Docker环境下的HTML转PDF(我们可以先把填充后的DocX转成HTML,再转PDF)。

代码示例

using DocX;
using DinkToPdf;
using DinkToPdf.Contracts;

public class PdfGeneratorService
{
    private readonly IConverter _converter;

    // 注入DinkToPdf转换器(在Program.cs中注册)
    public PdfGeneratorService(IConverter converter)
    {
        _converter = converter;
    }

    // 第一步:将原始DocX转换成带占位符的模板
    public void CreateDocxTemplate(string originalDocxPath, string templatePath)
    {
        using (var document = DocX.Load(originalDocxPath))
        {
            // 替换原始内容为占位符,可批量添加规则
            document.ReplaceText("John Doe", "#NAME_PLACEHOLDER");
            document.ReplaceText("123 Main St", "#ADDRESS_PLACEHOLDER");
            
            document.SaveAs(templatePath);
        }
    }

    // 第二步:基于模板填充数据并生成PDF
    public byte[] GeneratePdfFromTemplate(string templatePath, Dictionary<string, string> data)
    {
        using (var document = DocX.Load(templatePath))
        {
            // 填充动态数据
            foreach (var kvp in data)
            {
                document.ReplaceText(kvp.Key, kvp.Value);
            }

            // 将DocX转成HTML(DinkToPdf支持HTML转PDF)
            var htmlContent = document.SaveAsHtml();

            // 配置PDF转换参数
            var pdfConfig = new HtmlToPdfDocument()
            {
                GlobalSettings = {
                    ColorMode = ColorMode.Color,
                    Orientation = Orientation.Portrait,
                    PaperSize = PaperKind.A4,
                },
                Objects = {
                    new ObjectSettings() { HtmlContent = htmlContent }
                }
            };

            // 生成PDF字节数组,方便API直接返回
            return _converter.Convert(pdfConfig);
        }
    }
}

场景2:直接修改PDF模板

如果需要直接操作PDF,推荐使用适配.NET Core的iTextSharp分支:

依赖库安装

Install-Package iTextSharp.LGPLv2.Core

代码示例

using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;

public byte[] ReplacePdfPlaceholders(string templatePdfPath, Dictionary<string, string> data)
{
    using (var originalStream = new FileStream(templatePdfPath, FileMode.Open))
    using (var outputStream = new MemoryStream())
    {
        var reader = new PdfReader(originalStream);
        var stamper = new PdfStamper(reader, outputStream);
        var acroFields = stamper.AcroFields;

        // 如果PDF是表单模板(带可填写字段),直接填充字段更简单
        foreach (var kvp in data)
        {
            if (acroFields.Fields.ContainsKey(kvp.Key))
            {
                acroFields.SetField(kvp.Key, kvp.Value);
            }
        }

        // 如果是普通文本替换(非表单),遍历每页查找文本并替换
        var pages = reader.NumberOfPages;
        for (int i = 1; i <= pages; i++)
        {
            var strategy = new ReplaceTextStrategy(data);
            PdfTextExtractor.GetTextFromPage(reader, i, strategy);
            stamper.GetOverContent(i).Add(strategy.GetUpdatedContent());
        }

        stamper.FormFlattening = true; // 扁平化表单,防止后续修改
        stamper.Close();
        reader.Close();

        return outputStream.ToArray();
    }
}

// 自定义文本替换策略
public class ReplaceTextStrategy : LocationTextExtractionStrategy
{
    private readonly Dictionary<string, string> _replacements;
    private readonly List<Rectangle> _textLocations = new List<Rectangle>();
    private string _currentText;

    public ReplaceTextStrategy(Dictionary<string, string> replacements)
    {
        _replacements = replacements;
    }

    public override void RenderText(TextRenderInfo renderInfo)
    {
        base.RenderText(renderInfo);
        _currentText = renderInfo.GetText();
        _textLocations.Add(renderInfo.GetDescentLine().GetBoundingRectangle());
    }

    public PdfContentByte GetUpdatedContent()
    {
        var content = new PdfContentByte(null);
        foreach (var rect in _textLocations)
        {
            // 先覆盖原文本(用白色填充)
            content.SetColorFill(BaseColor.WHITE);
            content.Rectangle(rect.Left, rect.Bottom, rect.Width, rect.Height);
            content.Fill();

            // 写入替换后的文本,注意匹配原字体大小
            var replacementText = _replacements.TryGetValue(_currentText, out var val) ? val : _currentText;
            content.SetColorFill(BaseColor.BLACK);
            content.BeginText();
            content.SetFontAndSize(BaseFont.CreateFont(), 12);
            content.ShowTextAligned(Element.ALIGN_LEFT, replacementText, rect.Left, rect.Bottom, 0);
            content.EndText();
        }
        return content;
    }
}

三、Docker环境配置

因为用到了DinkToPdf(依赖wkhtmltopdf),需要在Dockerfile中安装对应的系统依赖:

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80

# 安装wkhtmltopdf及依赖库
RUN apt-get update && apt-get install -y \
    wkhtmltopdf \
    libgdiplus \
    libc6-dev

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["YourProjectName.csproj", "."]
RUN dotnet restore "./YourProjectName.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "YourProjectName.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "YourProjectName.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "YourProjectName.dll"]

注意:如果是.NET 7/8,替换对应的基础镜像即可。


四、实用小贴士

  • 占位符尽量用独特格式,比如{{NAME}}或者#NAME_PLACEHOLDER,避免和文档中的正常内容冲突
  • 如果DocX模板有复杂格式(如表格、图片),用DocX替换占位符时不会破坏原有格式,比直接操作PDF靠谱
  • 大文件处理时,尽量用流操作(MemoryStream)代替文件读写,提升性能
  • Docker中如果出现字体缺失导致PDF乱码,可以把自定义字体打包到镜像中,在代码中指定字体路径

内容的提问来源于stack exchange,提问作者MortenMoulder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:43:38