asp.net core web 解决方案多项目模板制作打包总结
一、文件夹\项目结构
1.1、文件夹
net6.0:针对.net 6.0 项目模板
net6.0pack:针对net6.0打包
1.2、项目结构
Web\WebApi多项目、各层项目、单元测试项目
目标:制作Web\WebApi两个项目模板
二、模板参数
2.1、template\net6.0\.template.config\template.json
{
"$schema": "http://json.schemastore.org/template",
"author": "yinyunpan",
"classifications": [ "Web", "WebApi" ],
"name": "项目模板案例",
"identity": "sample.template",
"shortName": "st",
"tags": {
"language": "C#",
//解决方案,与HostIdentifier用法生成解决方案文件名呼应
"type": "solution"
},
"sourceName": "sampletemplate",
"preferNameDirectory": false,
//重新生成项目guid,对应template.sln中项目guid
"guids": [
"5A6E6B9F-F8FA-4152-95B4-860F8CCAE3C3",
"30B12CFC-11E1-4E1C-B484-F708CEBD080A",
"810CD02B-0E13-4123-A84F-FBD4F4F1CD3A",
"3D2A540B-E773-44FA-82CF-14F3393D41B2",
"F4C1EFA8-824C-4985-A50D-BD1171ED1CFC",
"41C74675-6234-41F2-9B8A-4D06159D2A81",
"5EAD3BC3-554B-4058-9A9D-A91DF5F2DE1C",
"8A522BE9-0EE5-4C54-861B-394452A6744E",
"F075845E-43F4-466D-B731-904782BD9047"
],
"symbols": {
/**
template.sln文件有用host,排除项目文件引用
modifiers-condition,排除文件夹+文件
**/
"host": {
"type": "parameter",
"datatype": "choice",
"choices": [
{
"choice": "web",
"description": "后台"
},
{
"choice": "webapi",
"description": "接口"
}
],
"defaultValue": "web",
"description": "项目类型",
"displayName": "项目类型",
"isRequired": true
},
"web": {
"type": "computed",
"value": "(host == \"web\")"
},
"webapi": {
"type": "computed",
"value": "(host == \"webapi\")"
},
//应用的环境变量
"appUK": {
"type": "parameter",
"datatype": "string",
"description": "应用标识",
"displayName": "应用标识",
"isRequired": true,
"replaces": "wutong.netcore.sampletemplate"
},
"kestrelHttpPort": {
"type": "parameter",
"datatype": "integer",
"description": "启动配置文件的http端口",
"displayName": "http端口"
},
"kestrelHttpPortGenerated": {
"type": "generated",
"generator": "port",
"parameters": {
"low": 5000,
"high": 5300
}
},
"kestrelHttpPortReplacer": {
"type": "generated",
"generator": "coalesce",
"parameters": {
"sourceVariableName": "kestrelHttpPort",
"fallbackVariableName": "kestrelHttpPortGenerated"
},
//与launchSettings.json端口一致,才能随机生成替换
"replaces": "5000"
},
//模板主机标识
"HostIdentifier": {
"type": "bind",
"binding": "HostIdentifier"
}
},
"sources": [
{
"exclude": [ ".template.config/**/*", ".vs/**/*" ],
"modifiers": [
{
"condition": "(web)",
"exclude": [ "src/WebApi/**/*", "test/WebApiUnitTest/**/*" ]
},
{
"condition": "(webapi)",
"exclude": [ "src/Web/**/*", "test/WebUnitTest/**/*" ]
},
{
/**
visual studio创建模板会自动生成新的解决方案文件.sln、与模板下{sourceName}.sln冲突,导致生成visual studio提醒外部文件变化重新加载,操作不友好
判断使用模板主机标识:vs-visual studio、dotnetcli\dotnetcli-preview-命令窗口
模板下解决方案文件名要非{sourceName}.sln,命令窗口重命名
参考:https://github.com/sayedihashimi/template-sample/tree/main/src/Samples/06-console-csharp-fsharp
**/
"condition": "(HostIdentifier == \"dotnetcli\" || HostIdentifier == \"dotnetcli-preview\")",
"rename": {
"template.sln": "sampletemplate.sln"
}
}
]
}
]
}
总结:
host定义项目类型,方便使用在host基础上定义web、webapi计算型布尔变量。
项目开发中本地启动文件(launchSettings.json)中会经常用环境变量,如:应用标识。
模板文件中关键字替换依赖环境变量的replaces参数,需要注意保证replaces的值与文件被替换的值一样,如:http端口、应用标识。
根据项目类型,排除web\webapi对应的项目以及单元测试项目文件。
如果需要区分当前使用模板场景(visual studio 可视化窗口、命令行窗口),有不同执行行为,可以使用HostIdentifier变量。如:解决文件.sln生成。
2.2、template\net6.0\.template.config\ide.host.json
{
"$schema": "http://json.schemastore.org/vs-2017.3.host",
"order": 200,
"icon": "ide/sampletemplate.png",
"symbolInfo": [
{
"id": "host",
"isVisible": true
},
{
"id": "appUK",
"isVisible": true
},
//隐藏http端口,自动生成
{
"id": "kestrelHttpPort",
"isVisible": false
}
]
}
2.3、template\net6.0\.template.config\dotnetcli.host.json
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
"host": {
"isHidden": false
},
"appUK": {
"isHidden": false
},
//隐藏http端口,自动生成
"kestrelHttpPort": {
"isHidden": true
}
}
}
三、解决方案文件设置
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32414.318
MinimumVisualStudioVersion = 10.0.40219.1
<!--#if(web)-->
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "src\Web\Web.csproj", "{F075845E-43F4-466D-B731-904782BD9047}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebUnitTest", "test\WebUnitTest\WebUnitTest.csproj", "{8A522BE9-0EE5-4C54-861B-394452A6744E}"
EndProject
<!--#endif-->
<!--#if(webapi)-->
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "src\WebApi\WebApi.csproj", "{5A6E6B9F-F8FA-4152-95B4-860F8CCAE3C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApiUnitTest", "test\WebApiUnitTest\WebApiUnitTest.csproj", "{5EAD3BC3-554B-4058-9A9D-A91DF5F2DE1C}"
EndProject
<!--#endif-->
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "src\Shared\Shared.csproj", "{30B12CFC-11E1-4E1C-B484-F708CEBD080A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain.Interfaces", "src\Domain.Interfaces\Domain.Interfaces.csproj", "{810CD02B-0E13-4123-A84F-FBD4F4F1CD3A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain.Service", "src\Domain.Service\Domain.Service.csproj", "{3D2A540B-E773-44FA-82CF-14F3393D41B2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure.Interfaces", "src\Infrastructure.Interfaces\Infrastructure.Interfaces.csproj", "{F4C1EFA8-824C-4985-A50D-BD1171ED1CFC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure.Implements", "src\Infrastructure.Implements\Infrastructure.Implements.csproj", "{41C74675-6234-41F2-9B8A-4D06159D2A81}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
<!--#if(web)-->
{F075845E-43F4-466D-B731-904782BD9047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F075845E-43F4-466D-B731-904782BD9047}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F075845E-43F4-466D-B731-904782BD9047}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F075845E-43F4-466D-B731-904782BD9047}.Release|Any CPU.Build.0 = Release|Any CPU
{8A522BE9-0EE5-4C54-861B-394452A6744E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A522BE9-0EE5-4C54-861B-394452A6744E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A522BE9-0EE5-4C54-861B-394452A6744E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A522BE9-0EE5-4C54-861B-394452A6744E}.Release|Any CPU.Build.0 = Release|Any CPU
<!--#endif-->
<!--#if(webapi)-->
{5A6E6B9F-F8FA-4152-95B4-860F8CCAE3C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A6E6B9F-F8FA-4152-95B4-860F8CCAE3C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A6E6B9F-F8FA-4152-95B4-860F8CCAE3C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A6E6B9F-F8FA-4152-95B4-860F8CCAE3C3}.Release|Any CPU.Build.0 = Release|Any CPU
{5EAD3BC3-554B-4058-9A9D-A91DF5F2DE1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5EAD3BC3-554B-4058-9A9D-A91DF5F2DE1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5EAD3BC3-554B-4058-9A9D-A91DF5F2DE1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5EAD3BC3-554B-4058-9A9D-A91DF5F2DE1C}.Release|Any CPU.Build.0 = Release|Any CPU
<!--#endif-->
{30B12CFC-11E1-4E1C-B484-F708CEBD080A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{30B12CFC-11E1-4E1C-B484-F708CEBD080A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30B12CFC-11E1-4E1C-B484-F708CEBD080A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30B12CFC-11E1-4E1C-B484-F708CEBD080A}.Release|Any CPU.Build.0 = Release|Any CPU
{810CD02B-0E13-4123-A84F-FBD4F4F1CD3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{810CD02B-0E13-4123-A84F-FBD4F4F1CD3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{810CD02B-0E13-4123-A84F-FBD4F4F1CD3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{810CD02B-0E13-4123-A84F-FBD4F4F1CD3A}.Release|Any CPU.Build.0 = Release|Any CPU
{3D2A540B-E773-44FA-82CF-14F3393D41B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D2A540B-E773-44FA-82CF-14F3393D41B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D2A540B-E773-44FA-82CF-14F3393D41B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D2A540B-E773-44FA-82CF-14F3393D41B2}.Release|Any CPU.Build.0 = Release|Any CPU
{F4C1EFA8-824C-4985-A50D-BD1171ED1CFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4C1EFA8-824C-4985-A50D-BD1171ED1CFC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4C1EFA8-824C-4985-A50D-BD1171ED1CFC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4C1EFA8-824C-4985-A50D-BD1171ED1CFC}.Release|Any CPU.Build.0 = Release|Any CPU
{41C74675-6234-41F2-9B8A-4D06159D2A81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{41C74675-6234-41F2-9B8A-4D06159D2A81}.Debug|Any CPU.Build.0 = Debug|Any CPU
{41C74675-6234-41F2-9B8A-4D06159D2A81}.Release|Any CPU.ActiveCfg = Release|Any CPU
{41C74675-6234-41F2-9B8A-4D06159D2A81}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2A1729E7-0ED9-48D5-84EB-897B28B15D78}
EndGlobalSection
EndGlobal
总结:
根据项目类型,保留对应项目引用。
四、开发测试
4.1、打包项目文件
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageType>Template</PackageType>
<PackageVersion>1.0.0.1</PackageVersion>
<PackageId>sample.template</PackageId>
<Title>项目模板案例</Title>
<Authors>flightengine</Authors>
<Description>项目模板案例</Description>
<PackageTags>dotnet-new;templates</PackageTags>
<TargetFramework>net6.0</TargetFramework>
<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
</PropertyGroup>
<ItemGroup>
<!--PackagePath="content" 保持文件夹\文件的层级结构,否则所有文件都在根目录-->
<Content Include="..\net6.0\**\*" PackagePath="content" Exclude="..\net6.0\**\bin\**;..\net6.0\**\obj\**;..\net6.0\**\.vs\**" />
<Compile Remove="..\net6.0\**\*" />
</ItemGroup>
</Project>
总结:
PackagePath定义很重要❗❗❗
4.2、编译打包
net6.0pack文件夹下执行:
dotnet pack
4.3、安装包
dotnet new --install sample.template.1.0.0.1.nupkg
生成到 net6.0pack\bin\Debug 文件夹
4.4、卸载包
dotnet new --uninstall sample.template
开发中修改后,先执行卸载再安装模板,后vs或者cli测试。
4.5、安装
visual studio:
勾选避免生成的代码层次与模板项目定义不一致。
4.6、上传
执行批处理文件
@echo off
:: 解决中文乱码
chcp 65001
set PackageVersion="1.0.0.1"
del .\PublishNuget\*.nupkg
dotnet clean .\net6.0pack.csproj
dotnet pack .\net6.0pack.csproj -p:PackageVersion=%PackageVersion% -c Release -o .\PublishNuget
:: dotnet nuget push 上传包源服务器
pause
生成到 net6.0pack\PublishNuget 文件夹,可以再结合上传包源服务器命令。
五、案例源码
https://github.com/yinyunpan/template
六、参考
6.1、官方或者第三方包参考
https://dotnetnew.azurewebsites.net/
搜索包,然后到 https://www.nuget.org/ 下载,分析别人是如何实现的。
6.2、文档
https://github.com/dotnet/templating/wiki/Reference-for-template.json
官网文档各个参数含义,有些特殊参数注释\案例写不是很明白。👎
https://github.com/sayedihashimi/template-sample
实际开发中遇到各种问题场景的分析及其解决办法,总结的很不错。👍