Dotnet Templating 定制自己的项目模板

Dotnet Templating 定制自己的项目模板

本讲解,如何借助 [.NET Core Template Engine](dotnet templating 定制自己的项目模板) 创建一个简单的项目模板。

前言

为了方便开发,.Net 提供了官方基本的项目模板如:ASP.NET Core Web、ASP.NET Core Web API 等,但是这只能帮助我们创建最基本的项目模板,所以很多第三方库为了方便开发者快速集成都提供了自己的项目模板,如:IdentityServer4.Templates。同样我们也可以根据自己的技术栈,创建出适用于自己或者公司的项目模板。

创建项目代码和配置文件

首先创建一个 Minimal APIs 项目,基于这个项目定义自己的项目模板。Template Engine 脱离$safeprojectname$这种项目模板参数的方式,好处是模板项目本身始终是一个可以正常编译运行的项目,可以及时发现制作模板过程中的问题。

大致的目录结构如下所示,可以根据实际使用中是否需要一个 nupkg 文件中包含多个项目模板调整结构。

1
2
3
4
5
6
7
8
src
├── content
│ ├── .template.config
│ │ ├── template.png
│ │ ├── template.json
│ │ ├── dotnetcli.host.json
│ │ └── ide.host.json
│ ├── 项目文件

.template.config 介绍

在项目根目录下创建.template.config 文件夹,存在项目相关配置文件。文件夹内创建三个 json 文件

  • template.json 必须的模板配置文件,定义一些模板配置信息以及模板生成的逻辑等
  • ide.host.json 非必须的 VS 界面配置文件,定义模板图标以及配置展示参数等
  • dotnetcli.host.json 非必须的 Cli 配置文件,定义命令中参数的短名称

[官方文档](dotnet new 自定义模板 - .NET CLI | Microsoft Docs)中给了 template.json 文件必须参数的说明。

除了这三个文件还可以增加多语言的支持,具体用法可以参考aspnetcore 的项目模板

修改.template.config 下的文件

template.json

对 symbols 字段做一些说明,其他必须参数上面的图片中有说明。这里我定义了四个参数:Framework,UseAuthorize,SeparateRouteHandler,IsLast。Framework 和 IsLast 只作演示,UseAuthorize,SeparateRouteHandler 两个参数是定制模板时需要用到的参数。

Framework 参数的 datatype 为 choice 类型,vs 中会展示一个下拉列表。replaces 的指定当前我们选择的值替换项目中的哪个值,类似 sourceName。其他字段均为字面意思。

UseAuthorize,SeparateRouteHandler 两个类型为 bool 类型,UseAuthorize 是否引入认证鉴权,演示代码中的判断。SeparateRouteHandler 演示模板的文件排除逻辑,生成项目时根据该值判断排除多余文件。

IsLast 参数则是简单的演示计算参数。

sources 参数用来自定义输出源,定义 modifiers 通过 condition 指定一个条件包含或者排除哪些文件,本文使用的是 exclude。

primaryOutputs 参数自定义输出,这里只定义了项目名。

postActions 则是定义模板生成后的动作,支持的 Action 参考文档

更多介绍(如生成 Guid,随机数等)参考Wiki

示例文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
{
"$schema": "http://json.schemastore.org/template",
"author": "Masa",
"classifications": ["web", "api"],
"identity": "Masa.web.tmpl",
"name": "Masa Web Api",
"shortName": "masa.tmpl",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "Masa.Web",
"preferNameDirectory": true,
"sources": [
{
"modifiers": [
{
"condition": "(!SeparateRouteHandler)",
"exclude": ["DemoHandler.cs"]
}
]
}
],
"symbols": {
"Framework": {
"type": "parameter",
"description": "The target .net framework for the project.",
"datatype": "choice",
"choices": [
{
"choice": "net6.0",
"description": "Target net6.0"
},
{
"choice": "net5.0",
"description": "Target net5.0"
}
],
"replaces": "net6.0",
"defaultValue": "net6.0"
},
"UseAuthorize": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "this application add authorize."
},
"SeparateRouteHandler": {
"type": "parameter",
"datatype": "bool",
"defaultValue": "false",
"description": "route handler use a separate file."
},
"IsLast": {
"type": "computed",
"value": "(Framework == \"net6.0\")"
}
},
"primaryOutputs": [
{
"path": "Masa.Web.csproj"
}
],
"postActions": [
{
"description": "Restore NuGet packages required by this project.",
"manualInstructions": [
{
"text": "Run 'dotnet restore'"
}
],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}
]
}

exclude 指定路径数组支持 Dic/** 以及 Dic/*.cs 的方式

ide.host.json

该文件只有两个简单的定义,通过 icon 指定模板图标(相对路径),symbolInfo 定义参数信息,如 vs 上显示哪些参数以及参数说明和默认值等。

示例文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
{
"$schema": "http://json.schemastore.org/vs-2017.3.host",
"icon": "WebAPI.png",
"symbolInfo": [
{
"id": "Framework",
"name": {
"text": ".net framework"
},
"description": {
"text": ".net framework for the project"
},
"isVisible": true,
"defaultValue": "net6.0"
},
{
"id": "UseAuthorize",
"name": {
"text": "the project use authorize"
},
"isVisible": true
},
{
"id": "SeparateRouteHandler",
"name": {
"text": "separate route handler file"
},
"invertBoolean": false,
"isVisible": true
}
]
}

invertBoolean 参数的值为 true 时会将 bool 类型参数的值反转,如 SeparateRouteHandler 的值在指定为 true 时,模板的判断逻辑代码中这个值会变为 false。

dotnetcli.host.json

cli 的 json 文件中只定义了一个 symbolInfo 参数,定义命令行方式创建模板时参数的短名称。

示例文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
"Framework": {
"longName": "framework"
},
"UseAuthorize": {
"longName": "use-authorize",
"shortName": "au"
},
"SeparateRouteHandler": {
"longName": "separate-route-handler",
"shortName": "separate"
}
}
}

自定义模板

仅列出.Net 相关项目文件,其它类型文件可参考自定义语言操作符部分。

自定义 CS 文件

cs 文件自定义使用类似预编译指令的方式 ,使用 #if ,#endif等命令自定义输出逻辑

1
2
3
4
5
6
#if (SeparateRouteHandler)
using Masa.Web;
#endif
#if(UseAuthorize)
using Microsoft.AspNetCore.Authentication.JwtBearer;
#endif

如果判断流程复杂且模板文件变动较大可以参考 aspnetcore 官网模板实例,定义两个独立文件再通过 rename 和 exclude 控制模板输出内容。

1
2
3
4
5
6
7
{
"condition": "(UseMinimalAPIs && (IndividualAuth || OrganizationalAuth))",
"rename": {
"Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs": "Program.cs"
},
"exclude": ["Program.MinimalAPIs.WindowsOrNoAuth.cs"]
}

自定义 csproj 文件

csproj 文件使用注释的方式,判断参数控制逻辑

1
2
3
<!--#if(UseAuthorize)-->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" />
<!--#endif-->

自定义 json 文件

json 文件同样使用注释的方式

1
2
3
4
5
//#if(EnableOpenAPI)
"launchUrl": "swagger",
//#else
"launchUrl": "weatherforecast",
//#endif

自定义语言操作符

语言操作符即模板引擎识别为流程控制的标记,通常为对应文件类型的注释标记,在Github列出了支持的文件类型和操作符。

可以在 template.json 中增加 SpecialCustomOperations 节点,扩展默认不支持的文件类型或者重写现有的操作符的定义。

如增加 md 文件的语言操作符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"SpecialCustomOperations": {
"**/*.md": {
"operations": [
{
"type": "conditional",
"configuration": {
"if": ["---#if"],
"else": ["---#else"],
"elseif": ["---#elseif", "---#elif"],
"endif": ["---#endif"],
"trim" : "true",
"wholeLine": "true",
}
}
]
}
}

然后 md 文件中定义模板输出:

1
2
3
4
5
6
7
8
9
# This is an example Markdown

---#if (FooBar)
Foo bar
---#elif (BarBaz)
Bar baz
---#else
Baz qux
---#endif

安装、卸载模板

在项目根目录下执行dotnet new -i ./  命令安装自定义的模板。

卸载模板命令:dotnet new -u ./   如果不是模板目录则需要指定模板全路径

模板发布到 nuget,参考官网模板定义一个templates.nuspec文件,执行 nuget.exe pack 命令即可。

作者

MASA

发布于

2021-12-02

更新于

2022-08-04

许可协议