dotnet templating 定制自己的项目模板
由于工作需要,研究了一下VS 项目模板生成的相关内容,本文做一下记录借助 .NET Core Template Engine创建一个加单的项目模板。
创建项目代码和配置文件
首先创建一个Minimal APIs项目,基于这个项目定义自己的项目模板。Template Engine 脱离$safeprojectname$这种项目模板参数的方式,好处是模板项目本身始终是一个可以正常编译运行的项目,可以及时发现制作模板过程中的问题。
.template.config介绍
在项目根目录下创建.template.config文件夹,存在项目相关配置文件。文件夹内创建三个json文件
- template.json 必须的模板配置文件,定义一些模板配置信息以及模板生成的逻辑等
- ide.host.json 非必须的VS界面配置文件,定义模板图标以及配置展示参数等
- dotnetcli.host.json 非必须的Cli配置文件,定义命令中参数的短名称
官方文档中给了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。
postActions则是定义模板生成后的动作,支持的Action参考文档。
更多介绍(如生成Guid,随机数等)参考Wiki
示例文件内容:
{ "$schema": "http://json.schemastore.org/template", "author": "Stacking", "classifications": ["web", "api"], "identity": "stacking.web.tmpl", "name": "Stacking Web Api", "shortName": "stack.tmpl", "tags": { "language": "C#", "type": "project" }, "sourceName": "Stacking.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": "Stacking.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上显示哪些参数以及参数说明和默认值等。
示例文件内容:
{ "$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参数,定义命令行方式创建模板时参数的短名称。
示例文件内容:
{ "$schema": "http://json.schemastore.org/dotnetcli.host", "symbolInfo": { "Framework": { "longName": "framework" }, "UseAuthorize": { "longName": "use-authorize", "shortName": "au" }, "SeparateRouteHandler": { "longName": "separate-route-handler", "shortName": "separate" } } }
自定义模板
自定义CS文件
cs文件自定义使用类似预编译指令的方式 ,使用#if,#endif等命令自定义输出逻辑
#if (SeparateRouteHandler) using Stacking.Web; #endif #if(UseAuthorize) using Microsoft.AspNetCore.Authentication.JwtBearer; #endif
如果判断流程复杂且模板文件变动较大可以参考aspnetcore官网模板实例,定义两个独立文件再通过rename和exclude控制模板输出内容。
{ "condition": "(UseMinimalAPIs && (IndividualAuth || OrganizationalAuth))", "rename": { "Program.MinimalAPIs.OrgOrIndividualB2CAuth.cs": "Program.cs" }, "exclude": [ "Program.MinimalAPIs.WindowsOrNoAuth.cs" ] }
自定义csproj文件
csproj文件使用注释的方式,判断参数控制逻辑
<!--#if(UseAuthorize)--> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.0" /> <!--#endif-->
自定义json文件
json文件同样使用注释的方式
//#if(EnableOpenAPI) "launchUrl": "swagger", //#else "launchUrl": "weatherforecast", //#endif
自定义语言操作符
语言操作符即模板引擎识别为流程控制的标记,通常为对应文件类型的注释标记,在Github列出了支持的文件类型和操作符。
可以在template.json中增加SpecialCustomOperations节点,扩展默认不支持的文件类型或者重写现有的操作符的定义。
如增加md文件的语言操作符:
"SpecialCustomOperations": {
"**/*.md": {
"operations": [
{
"type": "conditional",
"configuration": {
"if": ["---#if"],
"else": ["---#else"],
"elseif": ["---#elseif", "---#elif"],
"endif": ["---#endif"],
"trim" : "true",
"wholeLine": "true",
}
}
]
}
}
然后再md文件中定义模板输出:
# 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命令即可。
最终效果