asp.net core web 解决方案多项目模板制作打包总结

乎语百科 302 0

一、文件夹\项目结构

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

实际开发中遇到各种问题场景的分析及其解决办法,总结的很不错。

标签:

留言评论

  • 这篇文章还没有收到评论,赶紧来抢沙发吧~