我不再叫丫头 發表於 2024-6-21 00:45:00

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

<h1 id="写在前面">写在前面</h1>
<p>其实很多公司或者资深的开发都有自己快速创建项目的脚手架的,有的是魔改代码生成器实现,有的直接基于T4,RazorEngine等模板引擎打造;但无论如何,其最终目的其实就是搭建一个自定义项目模板(脚手架)。</p>
<p>今天我们聊聊:如何基于官方的cli <code>donet new</code> 命令创建自己的项目模板。</p>
<h1 id="什么是项目模板">什么是项目模板</h1>
<p>我想用一个命令来说明:</p>
<pre><code>dotnet new list
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/641760/202405/641760-20240516184226236-845384466.png" alt="image-20240515170358858" loading="lazy"></p>
<p>到这里大家就非常熟悉了,原来大家平时创建项目都是基于已有的模板创建的(红圈部分大家应该不陌生);我们今天目的就是创建一个这样的模板,并在vs新建项目时可供选择创建项目,或者使用cli命令直接创建;</p>
<p>当然,还有公开模板:</p>
<p>https://dotnetnew.azurewebsites.net/</p>
<h1 id="创建自己的模板">创建自己的模板</h1>
<h2 id="1先准备好一个项目">1、先准备好一个项目</h2>
<p>这里准备的项目就是平时普通的项目,后面会以这个项目为蓝本创建模板;因为我最近使用Azure Function类型项目比较多,我就以Function项目为例,其他类型项目同理的;</p>
<p><strong>项目结构图:</strong></p>
<p><img src="https://img2023.cnblogs.com/blog/641760/202405/641760-20240516184225700-586035648.png" alt="image-20240515171904545" loading="lazy"></p>
<p><strong>项目文件结构:</strong></p>
<pre><code>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
</code></pre>
<p>可以看到其实有很多跟构建,部署等有关的配置文件;</p>
<p><strong>Function1.cs</strong></p>
<pre><code>#模板项目的命名空间
namespace MyCompany.Cutapi.FunctionTemp
{
    public class Function1
    {
      private readonly Stopwatch _sw;
      private readonly IExtractSegmentService _extractSegmentService;
      private readonly ILogger&lt;Function1&gt; _logger;

      public Function1(IExtractSegmentService extractSegmentService, ILogger&lt;Function1&gt; logger)
      {
            _sw = new Stopwatch();
            _extractSegmentService = extractSegmentService;
            _logger = logger;
      }

                #模板项目的FunctionName 和一些跟队列有关的配置,这些后面都要
      
      
      public async Task&lt;VideoTranscodeNotify&gt; Run( 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} -&gt; function1 begin -&gt;{messageId}: {messageBody}");
            await messageActions.CompleteMessageAsync(message);

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

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

            return result;
      }
    }
}
</code></pre>
<p>以这个文件为例,模板项目里很多文件内容都可以按自定义参数被替换;当然文件名也可以替换;</p>
<h2 id="2创建配置文件">2、创建配置文件</h2>
<p>在项目根目录下创建配置文件:<code>/.template.config/template.json</code></p>
<p>结构如下:</p>
<p>├─.template.config<br>
│      template.json</p>
<p>内容:</p>
<pre><code>{
"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"
    }
}
}
</code></pre>
<p>更多参数请参考:https://github.com/dotnet/templating/wiki/Reference-for-template.json</p>
<h3 id="代码段过滤"><strong>代码段过滤</strong></h3>
<p>cs文件</p>
<pre><code>//EnableRedis是自定义参数
#if (EnableRedis)
            ConnectionMultiplexer redisConnection = ConnectionMultiplexer.Connect(AppSettings.GetConnectionString("Redis"));
            builder.Services.AddSingleton&lt;IConnectionMultiplexer&gt;(redisConnection);
            builder.Services.AddSingleton&lt;IDatabase&gt;(c =&gt; redisConnection.GetDatabase());
#endif
</code></pre>
<p>项目文件</p>
<pre><code>&lt;ItemGroup&gt;
&lt;PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" /&gt;
&lt;PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.9.0" /&gt;
&lt;PackageReference Include="Microsoft.Azure.WebJobs.Extensions.ServiceBus" Version="5.9.0" /&gt;
&lt;PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.1.1" /&gt;
&lt;PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" /&gt;
&lt;/ItemGroup&gt;
   
&lt;ItemGroup Condition="'$(EnableRedis)' == 'True' "&gt;
&lt;PackageReference Include="StackExchange.Redis" Version="2.6.48" /&gt;
&lt;/ItemGroup&gt;
</code></pre>
<h3 id="文件过滤">文件过滤</h3>
<p>模板文件加入如下配置</p>
<pre><code>"symbols":{...},
"sources": [
      {
          "modifiers": [
            {
                  "condition": "(!EnableRedis)", //EnableRedis!=true
                  "exclude": [ //排除下面的文件(这里仅做示例),后面的模板项目当设置参数:EnableRedis==false时,下面的文件就被过滤掉了
                  "src/MyCompany.Cutapi.FunctionTemp/Redis.cs",
                  ]
            }
          ]
      }
    ]   
</code></pre>
<h2 id="3执行模板安装">3、执行模板安装</h2>
<p>这一步是将根据配置文件,将普通项目安装成一个项目模板,理论上创建自定义模板到这步就完成了;</p>
<p>项目根目录执行:</p>
<pre><code>dotnet new install .
这里命令后面的`.` 是安装当前目录的项目的意思;

dotnet new install D:\MyCompany.Cutapi.FunctionTemp
也可以这样,用绝对路径
</code></pre>
<h3 id="更新模板">更新模板</h3>
<p>强制覆盖安装</p>
<pre><code>dotnet new install . --force
</code></pre>
<p>先删除再安装</p>
<pre><code>#先删除
dotnet new uninstall .

#重新安装
dotnet new install .
</code></pre>
<p>后面的<code>.</code>都代表在项目根目录执行,后面不再赘述;</p>
<h2 id="4检查安装结果">4、检查安装结果</h2>
<pre><code>dotnet new list
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/641760/202405/641760-20240516184225236-1229357108.png" alt="image-20240515180319820" loading="lazy"></p>
<p><img src="https://img2023.cnblogs.com/blog/641760/202405/641760-20240516184224758-1196806422.png" alt="image-20240515180417880" loading="lazy"></p>
<p>无论用cli还是vs 都可以看到我们项目模板了,创建模板成功;</p>
<p>参考</p>
<h2 id="5推送到nuget服务端可选">5、推送到nuget服务端(可选)</h2>
<p>这步是<strong>可选</strong>的! 注意!很多内部模板<strong>要脱密处理</strong>后再执行推送,请勿将机密信息推送到公网;</p>
<h3 id="1模板项目根目录创建文件mycompanycutapifunctiontempnuspec">1、模板项目根目录创建文件<code>MyCompany.Cutapi.FunctionTemp.nuspec</code></h3>
<pre><code>&lt;?xml version="1.0"?&gt;
&lt;package &gt;
&lt;metadata&gt;
        &lt;id&gt;HeinerFunction&lt;/id&gt;
        &lt;version&gt;1.0.0&lt;/version&gt;
        &lt;authors&gt;Heiner Wang&lt;/authors&gt;
        &lt;owners&gt;Heiner Wang&lt;/owners&gt;
        &lt;requireLicenseAcceptance&gt;false&lt;/requireLicenseAcceptance&gt;
        &lt;description&gt;xxx 公司 Azure Function 快速模板.&lt;/description&gt;
        &lt;tags&gt;dotnet-new;template&lt;/tags&gt;
&lt;/metadata&gt;
&lt;files&gt;
        &lt;file src="**\*" target="content"/&gt;
&lt;/files&gt;
&lt;/package&gt;
</code></pre>
<h3 id="2生成nuget包">2、生成nuget包</h3>
<p>在项目根目录执行</p>
<pre><code>nuget pack MyCompany.Cutapi.FunctionTemp.nuspec
</code></pre>
<p>生成nuget包:</p>
<p>HeinerFunction.1.0.0.nupkg</p>
<h3 id="3推送到服务端">3、推送到服务端</h3>
<pre><code>nuget push HeinerFunction.1.0.0.nupkg-Source https://api.nuget.org/v3/index.json -ApiKey YOUR_API_KEY
</code></pre>
<p>这步的--Source参数,如果你有搭建好自己的nuget服务端的话改成你自己的;</p>
<h1 id="如何使用一个模板">如何使用一个模板</h1>
<p>模板有了,怎么用这个就简单了;</p>
<h2 id="vs使用">vs使用</h2>
<p>在创建项目时直接选择自定义模板</p>
<p><img src="https://img2023.cnblogs.com/blog/641760/202405/641760-20240516184223854-273496505.png" alt="image-20240516093813918" loading="lazy"></p>
<p>不过这样的话,自定义参数都是用默认值,所以我还是更推荐用命令行方式;</p>
<h2 id="命令行使用推荐">命令行使用(推荐)</h2>
<p>大家做demo的时候都应该执行过这样的命令,其实这就是使用了官方shotname为<code>console</code>的模板</p>
<pre><code> dotnet new console -n MyConsoleApp1
</code></pre>
<p>一样,自定义模板命令为:</p>
<pre><code>#默认参数
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
</code></pre>
<p><strong>创建成功</strong></p>
<p><img src="https://img2023.cnblogs.com/blog/641760/202405/641760-20240516184223386-745490022.png" alt="image-20240516155502856" loading="lazy"></p>
<h1 id="参考">[参考]</h1>
<p>https://learn.microsoft.com/zh-cn/dotnet/core/tools/custom-templates</p>
<p>https://cloud.tencent.com/developer/article/2319366</p>
<p>https://github.com/dotnet/templating/wiki/Reference-for-template.json</p><br><br>
来源:https://www.cnblogs.com/xiaxiaolu/p/18259750
頁: [1]
查看完整版本: C#如何创建一个可快速重复使用的项目模板