C#如何创建一个可快速重复使用的项目模板

· 浏览次数 : 1

小编点评

本文介绍了如何基于官方的dotnet new命令创建自己的项目模板。项目模板是一种预定义的项目结构和配置,可以帮助开发人员快速创建具有相似功能和配置的新项目。 1. 准备一个项目 首先,需要准备一个普通的项目,作为创建模板的蓝本。项目结构应包含与构建、部署等相关的配置文件。 2. 创建配置文件 在项目根目录下创建一个配置文件(.template.config/template.json),用于定义模板的相关信息,如作者、项目归类、名称、唯一ID、简写、标签等。 3. 执行模板安装 在项目根目录下执行`dotnet new install .`命令,将普通项目安装成一个项目模板。如果需要强制覆盖安装,可以使用`dotnet new install . --force`命令。 4. 检查安装结果 执行`dotnet new list`命令,确认模板已经成功安装到项目中。 5. 推送到NuGet服务端(可选) 如果需要,可以将生成的NuGet包推送到NuGet服务端。注意,很多内部模板需要脱密处理后才能执行推送。 6. 使用模板 使用模板时,可以在Visual Studio中直接选择自定义模板,或者在命令行中使用`dotnet new`命令创建项目。指定相应的参数,如命名空间、函数名称、队列名称等。 通过以上步骤,可以创建一个自定义的项目模板,方便开发人员快速创建具有特定功能和配置的新项目。

正文

写在前面

其实很多公司或者资深的开发都有自己快速创建项目的脚手架的,有的是魔改代码生成器实现,有的直接基于T4,RazorEngine等模板引擎打造;但无论如何,其最终目的其实就是搭建一个自定义项目模板(脚手架)。

今天我们聊聊:如何基于官方的cli donet new 命令创建自己的项目模板。

什么是项目模板

我想用一个命令来说明:

dotnet new list

image-20240515170358858

到这里大家就非常熟悉了,原来大家平时创建项目都是基于已有的模板创建的(红圈部分大家应该不陌生);我们今天目的就是创建一个这样的模板,并在vs新建项目时可供选择创建项目,或者使用cli命令直接创建;

当然,还有公开模板:

https://dotnetnew.azurewebsites.net/

创建自己的模板

1、先准备好一个项目

这里准备的项目就是平时普通的项目,后面会以这个项目为蓝本创建模板;因为我最近使用Azure Function类型项目比较多,我就以Function项目为例,其他类型项目同理的;

项目结构图:

image-20240515171904545

项目文件结构:

D:.
│  appsettings.CI.json
│  appsettings.Development.json
│  appsettings.json
│  appsettings.Production.json
│  Dockerfile
│  Function1.cs
│  host.json
│  local.settings.json
│  MyCompany.Cutapi.FunctionTemp.csproj #这个名字后面要被替换的
│  settings.CI.yaml
│  settings.Production.yaml
│  Startup.cs
│
├─build
│      CD.yaml
│      CI.yaml
│      _deploy.yaml
│
└─deploy
    │  kustomization.yaml
    │
    ├─base
    │      deploy.yaml
    │      kustomization.yaml
    │
    ├─ci
    │      deploy.yaml
    │      kustomization.yaml
    │
    └─prod
            deploy.yaml
            kustomization.yaml

可以看到其实有很多跟构建,部署等有关的配置文件;

Function1.cs

#模板项目的命名空间
namespace MyCompany.Cutapi.FunctionTemp 
{
    public class Function1
    {
        private readonly Stopwatch _sw;
        private readonly IExtractSegmentService _extractSegmentService;
        private readonly ILogger<Function1> _logger;

        public Function1(IExtractSegmentService extractSegmentService, ILogger<Function1> logger)
        {
            _sw = new Stopwatch();
            _extractSegmentService = extractSegmentService;
            _logger = logger;
        }

		#模板项目的FunctionName 和一些跟队列有关的配置,这些后面都要
        [FunctionName("function1")]
        [return: ServiceBus("cutapi-queue1-notify", Connection = "ServiceBusConnection")]
        public async Task<VideoTranscodeNotify> Run([ServiceBusTrigger("cutapi-queue1", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message
            , string messageId
            , ServiceBusMessageActions messageActions
            , Int32 deliveryCount
            , DateTime enqueuedTimeUtc
            , ILogger log
            )
        {
            _sw.Start();
            var messageBody = Encoding.UTF8.GetString(message.Body);
            log.LogInformation($"{Environment.MachineName} -> function1 begin ->{messageId}: {messageBody}");
            await messageActions.CompleteMessageAsync(message);

            var result = new VideoTranscodeNotify();
            try
            {
                //todo...
            }
            catch (Exception ex)
            {
                log.LogError(ex, $"{Environment.MachineName} -> {messageId}:function1 Exception:{ex.Message}");
            }

            _sw.Stop();
            log.LogInformation($"{Environment.MachineName} function1 Over ->{messageId} Elapsed: {_sw.Elapsed}");

            return result;
        }
    }
}

以这个文件为例,模板项目里很多文件内容都可以按自定义参数被替换;当然文件名也可以替换;

2、创建配置文件

在项目根目录下创建配置文件:/.template.config/template.json

结构如下:

├─.template.config
│ template.json

内容:

{
  "author": "Heiner Wang", //作者
  "classifications": [ "Azure Functions" ], //项目归类 classifications 还会出现在“Tags”列中
  "name": "Heiner Function", //项目全名,用户应看到的模板名称。
  "identity": "HeinerFunction", //项目唯一id
  "shortName": "hfunc", //项目简写
  "tags": {
    "language": "C#",
    "type": "project"
  },
  "sourceName": "MyCompany.Cutapi.FunctionTemp", //运行模板时使用 -n 或 --name 选项提供要替换的值,不写了话项目名称不变
  "preferNameDirectory": true, //创建项目的目录层级;
  "symbols": { //自定义语法
    //自定义参数,新项目命名空间
    "Namespace": {
      "type": "parameter",
      "dataType": "text", //文本类型
      "defaultValue": "Heiner.Function",
      "replaces": "MyCompany.Cutapi.FunctionTemp" //项目里这个值将会被替换掉
      //"fileRename": "MyCompany.Cutapi.FunctionTemp" //也可以指定替换文件名
    },
    "FunctionName": {
      "type": "parameter",
      "dataType": "text",
      "defaultValue": "function1",
      "replaces": "function1"
    },
    "QueueName": {
      "type": "parameter",
      "dataType": "text",
      "defaultValue": "cutapi-queue1",
      "replaces": "cutapi-queue1"
    },
    "EnableRedis": {
      "type": "parameter",
      "dataType": "bool", #布尔类型的
      "defaultValue": "true"
    }
  }
}

更多参数请参考:https://github.com/dotnet/templating/wiki/Reference-for-template.json

代码段过滤

cs文件

//EnableRedis是自定义参数
#if (EnableRedis) 
            ConnectionMultiplexer redisConnection = ConnectionMultiplexer.Connect(AppSettings.GetConnectionString("Redis"));
            builder.Services.AddSingleton<IConnectionMultiplexer>(redisConnection);
            builder.Services.AddSingleton<IDatabase>(c => redisConnection.GetDatabase());
#endif

项目文件

<ItemGroup>
  <PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
  <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.9.0" />
  <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="5.9.0" />
  <PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" />
  <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
</ItemGroup>  
    
<ItemGroup Condition="'$(EnableRedis)' == 'True' ">
  <PackageReference Include="StackExchange.Redis" Version="2.6.48" />
</ItemGroup>  

文件过滤

模板文件加入如下配置

"symbols":{...},
"sources": [
      {
          "modifiers": [
              {
                  "condition": "(!EnableRedis)", //EnableRedis!=true
                  "exclude": [ //排除下面的文件(这里仅做示例),后面的模板项目当设置参数:EnableRedis==false时,下面的文件就被过滤掉了
                    "src/MyCompany.Cutapi.FunctionTemp/Redis.cs",  
                  ]
              }
          ]
      }
    ]    

3、执行模板安装

这一步是将根据配置文件,将普通项目安装成一个项目模板,理论上创建自定义模板到这步就完成了;

项目根目录执行:

dotnet new install .
这里命令后面的`.` 是安装当前目录的项目的意思;

dotnet new install D:\MyCompany.Cutapi.FunctionTemp
也可以这样,用绝对路径

更新模板

强制覆盖安装

dotnet new install . --force

先删除再安装

#先删除
dotnet new uninstall .

#重新安装
dotnet new install .

后面的.都代表在项目根目录执行,后面不再赘述;

4、检查安装结果

dotnet new list

image-20240515180319820

image-20240515180417880

无论用cli还是vs 都可以看到我们项目模板了,创建模板成功;

参考

5、推送到nuget服务端(可选)

这步是可选的! 注意!很多内部模板要脱密处理后再执行推送,请勿将机密信息推送到公网;

1、模板项目根目录创建文件MyCompany.Cutapi.FunctionTemp.nuspec

<?xml version="1.0"?>
<package >
<metadata>
	<id>HeinerFunction</id>
	<version>1.0.0</version>
	<authors>Heiner Wang</authors>
	<owners>Heiner Wang</owners>
	<requireLicenseAcceptance>false</requireLicenseAcceptance>
	<description>xxx 公司 Azure Function 快速模板.</description>
	<tags>dotnet-new;template</tags>
</metadata>
<files>
	<file src="**\*" target="content"/>
</files>
</package>

2、生成nuget包

在项目根目录执行

nuget pack MyCompany.Cutapi.FunctionTemp.nuspec

生成nuget包:

HeinerFunction.1.0.0.nupkg

3、推送到服务端

nuget push HeinerFunction.1.0.0.nupkg  -Source https://api.nuget.org/v3/index.json -ApiKey YOUR_API_KEY

这步的--Source参数,如果你有搭建好自己的nuget服务端的话改成你自己的;

如何使用一个模板

模板有了,怎么用这个就简单了;

vs使用

在创建项目时直接选择自定义模板

image-20240516093813918

不过这样的话,自定义参数都是用默认值,所以我还是更推荐用命令行方式;

命令行使用(推荐)

大家做demo的时候都应该执行过这样的命令,其实这就是使用了官方shotname为console的模板

 dotnet new console -n MyConsoleApp1

一样,自定义模板命令为:

#默认参数
dotnet new hfunc -n MyCompany.Heiner.Test 

#指定参数
dotnet new hfunc -n MyCompany.Heiner.Test  --Namespace MyCompany.Heiner.Test --FunctionName function-live-record --QueueName cutapi-live-record --EnableRedis false

创建成功

image-20240516155502856

[参考]

https://learn.microsoft.com/zh-cn/dotnet/core/tools/custom-templates

https://cloud.tencent.com/developer/article/2319366

https://github.com/dotnet/templating/wiki/Reference-for-template.json

与C#如何创建一个可快速重复使用的项目模板相似的内容:

C#如何创建一个可快速重复使用的项目模板

写在前面 其实很多公司或者资深的开发都有自己快速创建项目的脚手架的,有的是魔改代码生成器实现,有的直接基于T4,RazorEngine等模板引擎打造;但无论如何,其最终目的其实就是搭建一个自定义项目模板(脚手架)。 今天我们聊聊:如何基于官方的cli donet new 命令创建自己的项目模板。 什

gRPC入门学习之旅(十)

gRPC是一个高性能、通用的开源远程过程调用(RPC)框架,基于底层HTTP/2协议标准和协议层Protobuf序列化协议开发, gRPC 客户端和服务端可以在多种环境中运行和交互。你可以用Java创建一个 gRPC 服务端,用 Go、Python、C# 来创建客户端。本系统文章详细描述了如何创建一...

gRPC入门学习之旅(九)

gRPC是一个高性能、通用的开源远程过程调用(RPC)框架,基于底层HTTP/2协议标准和协议层Protobuf序列化协议开发, gRPC 客户端和服务端可以在多种环境中运行和交互。你可以用Java创建一个 gRPC 服务端,用 Go、Python、C# 来创建客户端。本系统文章详细描述了如何创建一...

gRPC入门学习之旅(八)

gRPC是一个高性能、通用的开源远程过程调用(RPC)框架,基于底层HTTP/2协议标准和协议层Protobuf序列化协议开发, gRPC 客户端和服务端可以在多种环境中运行和交互。你可以用Java创建一个 gRPC 服务端,用 Go、Python、C# 来创建客户端。本系统文章详细描述了如何创建一...

iOS 单元测试

作用一名合格的程序员,得能文能武。写的了代码,也要写的了单元测试。 单元测试步骤 1.File -> New -> Target, 选择单元测试Target,创建成功 如果项目是老项目,那需要手动创建一下UnitTest Target,如果项目里已经有了就忽略。 2.创建一个swift工具的测试类C

go NewTicker 得使用

转载请注明出处: 在 Go 语言中,time.NewTicker 函数用于创建一个周期性触发的定时器。它会返回一个 time.Ticker 类型的值,该值包含一个通道 C,定时器会每隔一段时间向通道 C 发送一个时间值。 NewTicker 函数的签名如下: func NewTicker(d Dur

new方法、定制属性访问、描述符与装饰器知识点总结

第一部分:__new__方法 思考: a. 我们创建实例是通过什么方法创建的呢? b. 类每次实例化的时候都会创建一个新的对象,如果要求类只能被实例化一次该怎么做呢? 通过单利模式实现 c.什么是单例模式(Singleton Pattern ) 1、确保一个类只有一个实例,而且自行实例化并向整个系统

[转帖]删除分区如何不让全局索引失效?

记得上次ACOUG年会(《ACOUG年会感想》),请教杨长老问题的时候,谈到分区,如果执行分区删除的操作,就会导致全局索引失效,除了使用12c以上版本能避免这个问题外,指出另外一种解决的方式,表面看很巧妙,实则是对分区原理的深入理解。 我们先从实验,了解这个问题,首先创建分区表,他存在4个分区,每个

Blazor实战——Known框架快速开始

Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行。 开源地址: https://gitee.com/known/Known ## 1. 安装项目模板并创建新项目 - 打开命令行输入如下命令安装和创建。 ```bash -- 安装模板 dotnet

如何不使用图形来创建ACFS文件系统

客户需求,提供在19c环境下,ACFS的命令行操作的具体步骤,便于在图形界面不可用场景使用。 当然,如果有图形可操作,还是推荐首选图形,避免复杂度以及不必要的错误。 其实之前有测试过11g环境下的ACFS命令创建,如下: - [通过命令行创建ACFS文件系统](https://www.cnblogs