一、环境概述

  • SDK:.NET Core 8.0

  • IDE:Visual Studio 2022

  • 项目骨架:标准控制台骨架

  • 模板引擎:Handlebars.Net

  • Json处理:Newtonsoft.Json

二、背景概述

项目上需要整理一份参数详细文档,然后参数有的有几百个,有的有一千多个,不可能手动整理,就想到通过结构化模板渲染输出文档。

三、相关依赖

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Handlebars.Net" Version="2.1.6" />
    <PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
  </ItemGroup>
</Project>

四、制作模板

# {{Title}}

[toc]

> 版本: {{Version}}  
> 作者: {{Author}}  
> 创建时间: {{formatDate CreatedDate}}  
> 描述: {{Description}}

---

## 参数详情 (共 {{DetailsCount}} 项)

{{#each Details}}

### {{Sequence}}. {{Name}} (ID: {{Id}})

- **类型**:{{ParamType}}
- **数据类型**:{{DataType}}

**内容**:

{{{Content}}}

**备注**:

{{{Remark}}}

{{/each}}

五、实体和常量

Document.cs

using Newtonsoft.Json;

namespace Online.Admin.Template.Models.DTOs
{
    public class Document
    {
        public Guid Id { get; set; }
        public string CriteriaNo { get; set; } = string.Empty;
        public string Author { get; set; } = string.Empty;
        public DateTime CreatedDate { get; set; }
        public string Version { get; set; } = string.Empty;
        public string Title { get; set; } = string.Empty;
        public string Description { get; set; } = string.Empty;
        [JsonIgnore]
        public int DetailsCount => Details?.Count ?? 0;
        public List<ParameterInfo> Details { get; set; } = new List<ParameterInfo>();
    }
}

ParameterInfo.cs

using Online.Admin.Template.Constant;
using System.Text.Json.Serialization;

namespace Online.Admin.Template.Models.DTOs
{
    /// <summary>
    /// 表示模板参数的详细信息。
    /// </summary>
    public class ParameterInfo
    {
        /// <summary>
        /// 参数显示顺序
        /// </summary>
        public int Sequence { get; set; }

        /// <summary>
        /// 参数唯一标识符
        /// </summary>
        public string Id { get; set; } = string.Empty;

        /// <summary>
        /// 参数编号
        /// </summary>
        public string Number { get; set; } = string.Empty;

        /// <summary>
        /// 参数显示名称
        /// </summary>
        public string Name { get; set; } = string.Empty;

        /// <summary>
        /// 参数类型
        /// </summary>
        [JsonConverter(typeof(JsonStringEnumConverter))]
        public ParameterType ParamType { get; set; } = ParameterType.Direct;

        /// <summary>
        /// 参数数据类型
        /// </summary>
        [JsonConverter(typeof(JsonStringEnumConverter))]
        public ParameterDataType DataType { get; set; } = ParameterDataType.Unknown;

        /// <summary>
        /// 参数内容
        /// </summary>
        public string Content { get; set; } = string.Empty;

        /// <summary>
        /// 备注说明
        /// </summary>
        public string Remark { get; set; } = string.Empty;
    }
}

ParameterDataType.cs

namespace Online.Admin.Template.Constant
{
    public enum ParameterDataType
    {
        /// <summary>
        /// 小数
        /// </summary>
        Decimal,
        /// <summary>
        /// 字符
        /// </summary>
        String,
        /// <summary>
        /// 整数
        /// </summary>
        Integer,
        /// <summary>
        /// 未知类型
        /// </summary>
        Unknown
    }
}

ParameterType.cs

namespace Online.Admin.Template.Constant
{
    public enum ParameterType
    {
        /// <summary>
        /// 字面量参数
        /// </summary>
        Direct,
        /// <summary>
        /// 派生参数
        /// </summary>
        Computed
    }
}

SystemConstant.cs

namespace Online.Admin.Template.Constant
{
    public class SystemConstant
    {
        public static readonly string TemplatesBasePath = "XXX\\Templates\\";
        public static readonly string JsonDataPath = "XXX\\Templates\\";
    }
}

六、数据准备

这里演示就直接通过json反序列化获取实体数据了,就不从数据库读取了

[
  {
    "sequence": 1,
    "id": "user_name",
    "number": "P001",
    "name": "用户名",
    "paramType": "Direct",
    "dataType": "String",
    "content": "{{UserName}}",
    "remark": "从上下文变量中获取"
  },
  {
    "sequence": 2,
    "id": "send_time",
    "number": "P002",
    "name": "发送时间",
    "paramType": "Computed",
    "dataType": "String",
    "content": "System.Now",
    "remark": "引用系统当前时间(暂用 String 表示)"
  },
  {
    "sequence": 3,
    "id": "is_vip",
    "number": "P003",
    "name": "是否 VIP",
    "paramType": "Direct",
    "dataType": "String",
    "content": "true",
    "remark": "固定值(布尔值暂用 String 表示)"
  }
]

七、渲染文档

就是这里要注意的是,有些公司电脑是big5编码字符集的,你发utf8字符集文档过去会乱码,所以生成的时候要注意字符集。

Program.cs

using HandlebarsDotNet;
using Online.Admin.Template.Constant;
using Online.Admin.Template.Models.DTOs;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var utf8 = Encoding.UTF8;

try
{
    var docDataPath = Path.Combine(SystemConstant.JsonDataPath, "Untitled-2.json");
    if (!File.Exists(docDataPath))
        throw new FileNotFoundException($"JSON 数据文件不存在: {docDataPath}");
    string jsonString = File.ReadAllText(docDataPath, utf8);

    var jsonOptions = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        Converters = { new JsonStringEnumConverter() },
        ReadCommentHandling = JsonCommentHandling.Skip,
        AllowTrailingCommas = true
    };
    var templateData = JsonSerializer.Deserialize<List<ParameterInfo>>(jsonString, jsonOptions)
                       ?? new List<ParameterInfo>();
    var document = new Document
    {
        Id = Guid.NewGuid(),
        Author = "张三",
        CreatedDate = DateTime.UtcNow,
        Version = "1.0.0",
        Title = "示例模板标题",
        Description = "这是一个用于演示的模板头信息。",
        Details = templateData
    };

    // === 4. 读取模板 ===
    var docTempFilePath = Path.Combine(SystemConstant.TemplatesBasePath, "Criteria_Param_Template1.md");
    if (!File.Exists(docTempFilePath))
        throw new FileNotFoundException($"模板文件不存在: {docTempFilePath}");

    string templateContent = File.ReadAllText(docTempFilePath, utf8);

    // === 5. 创建 Handlebars 实例并注册 Helper ===
    var handlebars = Handlebars.Create(new HandlebarsConfiguration
    {
        NoEscape = true
    });
    handlebars.RegisterHelper("formatDate", (writer, context, args) =>
    {
        if (args.Length > 0 && args[0] is DateTime dt)
        {
            writer.Write(dt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"));
        }
        else
        {
            writer.Write(string.Empty);
        }
    });

    // === 6. 编译并渲染 ===
    var compiledTemplate = handlebars.Compile(templateContent);
    string result = compiledTemplate(document); 
    await File.WriteAllTextAsync("output.md", result, utf8);

    Console.WriteLine("output.md 已生成");
}
catch (Exception ex)
{
    Console.WriteLine($"处理失败: {ex.Message}");
}

八、效果展示

这里用的MarkText打开的

2026-01-09-17-35-58-image