Criando templates para o dotnet new

Há poucos dias, trabalhando em um cliente na CWI surgiu a necessidade de fazer a criação de vários novos serviços. Precisávamos que estes serviços já fossem criados com alguns padrões de código e que estes padrões iniciais ficassem flexíveis para alteração do desenvolvedor que estivesse criando o projeto.

Inicialmente, discutimos a alternativa mais simples pra nós: ter um repositório de template para ser clonado e editado pelo desenvolvedor que está criando o novo projeto. Apesar de ser uma alternativa boa e efetiva (nas versões modernas do .NET) achamos que faria mais sentido termos templates para serem usados diretamente com a CLI do .NET (dotnet new). Dessa forma podemos distribuir um conjunto de templates úteis, não só de projetos, mas de itens criados frequentemente, para facilitar o dia a dia da equipe.

Neste post conto a experiência de como fizemos a criação de um template para a CLI do .NET.

Código do template

Primeiro, é necessário criar um novo projeto para conter o código do template. Isso pode ser feito de qualquer forma que se use para criar um projeto, eu costumo usar o CLI para isso, mas qualquer forma é válida.

1
 MeuProjeto-Templates/templates> dotnet new webapi --name Empresa.Template.WebApi

Depois é só adicionar o código necessário do template. Neste projeto precisávamos seguir alguns padrões bem definidos: injetar alguns serviços, configurar o pipeline do ASP.NET Core com alguns middlewares e criar arquivos web.config e appSettings.json com configurações padrões.

Além disso, era necessário passar um argumento para um dos métodos chamados na classe Program e este argumento, idealmente, deveria ser definido no nomento de executar o dotnet new.

Pra fins de entendimento, o código do arquivo Program.cs ficou desta forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using Empresa.Template.WebApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddDomainServices()
    .AddServicesWithParam(ParamDoServico);

var app = builder.Build();

if (Convert.ToBoolean(app.Configuration["UsarSwagger"]))
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseCors()
    .UseHsts()
    .UseAuthentication()
    .UseAuthorization()
    .UseForwardedHeaders();

app.MapControllers();

app.Run();

A linha .AddServicesWithParam(ParamDoServico); é especialmente importante aqui, porque queremos substituir este parâmetro pelo valor informado no momento da execução do dotnet new.

Configurando o template

Com o código pronto, é hora de fazer a configuração do template.

Para isso é necessário criar uma pasta chamada .template.config esta pasta deve estar um nível acima do arquivo .csproj. A nossa estrutura de pastas ficou assim:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
MeuProjeto-Templates
└───templates
    └───Empresa.Template.WebApi
        │    Empresa.Template.WebApi.csproj
        │    Program.cs
        │    web.config
        │    ...
        └───.template.config
                template.json

💡 O schema completo deste arquivo pode ser visto no JSON Schema Store.

O nosso template.json ficou parecido com este.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "$schema": "http://json.schemastore.org/template",
  "author": "CWI",
  "classifications": [
    "WebAPI"
  ],
  "identity": "Empresa.Template.WebApi",
  "name": "Nosso template de webapi",
  "shortName": "nosso-webapi",
  "tags": {
    "language": "C#",
    "type": "project"
  },
  "sourceName": "Empresa.Template.WebApi",
  "symbols": {
    "ParamServico": {
      "type": "parameter",
      "description": "Paramêtro usado na injeção do serviço X",
      "datatype": "text",
      "replaces": "ParamDoServico",
      "defaultValue": "Invalido"
    }
  }
}

Este JSON é basicamente autoexplicativo, mas é importante prestar atenção em dois detalhes:

  • A propriedade sourceName define um valor que será substituído em todos os arquivos pelo valor informado no argumento --name caso não haja alguma configuração para ignorar algum arquivo
  • A propriedade symbol configura um novo parâmetro para o dotnet new. Neste caso, estamos configurando um parâmetro chamado ParamServico que é do tipo texto e que seu valor substituirá a chave ParamDoServico no código, além disso é configurado um valor padrão caso este argumento não seja informado.

Testando o template

Agora o template está pronto e configurado pra ser usado. A CLI do .NET permite fazer a instalação de templates pelo sistema de arquivos mesmo e, para testar, é necessário executar a instalação e executar um dotnet new.

Instalação

1
 temp/teste-template> dotnet new --install ~/MeuProjeto-Templates/

A saída deve ser algo parecido com a abaixo.

1
2
3
4
5
6
7
8
The following template packages will be installed:
   ~/MeuProjeto-Templates/

Success: ~/MeuProjeto-Templates/ installed the following templates:

Template Name             Short Name    Language  Tags
------------------------  ------------  --------  ------
Nosso template de webapi  nosso-webapi  [C#]      WebAPI

Usando com dotnet new 🚀

Agora é só usar o template para criar novos projetos.

1
2
3
temp/teste-template> dotnet new nosso-webapi \
    --name EmpresaXXX.NomeProjeto.WebApi \
    --ParamServico XptoAbcd

Como esperado, nosso novo projeto foi criado com base no código do template. Note os namespaces ficaram certos (graças ao parâmetro sourceName) e o valor ParamDoServico foi substituído por XptoAbcd.

Empacotando

Depois de termos um template funcional, precisamos encontrar uma forma de compartilhar com nossos colegas. Compartilhar num sistema de arquivos acessível a todos os membros da equipe é uma opção válida, mas não é muito boa no nosso caso onde todo mundo trabalha remotamente e usamos VPN apenas para atividades específicas. A forma que achamos ideal foi compartilhar o template no nosso feed Nuget.

Pra fazer isso criamos um pacote nuget com todos os templates e fizemos o deploy deste pacote no feed acessível por toda a equipe.

Primeiro, criamos um novo projeto C# simples para facilitar a criação do csproj.

1
 MeuProjeto-Templates> dotnet new console -n NossoTemplatePack -o .

O arquivo Program.cs pode ser removido e o conteúdo do arquivo .csproj deve ficar parecido com o abaixo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <PackageType>Template</PackageType>
    <PackageVersion>1.0</PackageVersion>
    <PackageId>Empresa.Templates</PackageId>
    <Title>Nossos templates</Title>
    <Authors>CWI</Authors>
    <Description>Templates para usar ao criar uma nova aplicação</Description>
    <PackageTags>dotnet-new;templates</PackageTags>

    <TargetFramework>netstandard2.0</TargetFramework>
    <IncludeContentInPack>true</IncludeContentInPack>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <ContentTargetFolders>content</ContentTargetFolders>
    <NoWarn>$(NoWarn);NU5128</NoWarn>
    <NoDefaultExcludes>true</NoDefaultExcludes>
  </PropertyGroup>

  <ItemGroup>
    <Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**" />
    <Compile Remove="**\*" />
  </ItemGroup>
</Project>

O conteúdo deste arquivo foi baseado no exemplo da documentação.

O primeiro bloco define propriedades usadas na geração do pacote Nuget.

O segundo bloco inclui configurações relacionadas à configuração do projeto para incluir os templates na pasta apropriada no pacote NuGet quando ele é criado.

O terceiro bloco (em ItemGroup) define os itens que devem ser adicionados ou removidos no pacote gerado e que não deve haver compilação dos arquivos de código.


Depois disso, é possível empacotar os templates e distribui-lo em algum feed Nuget.

1
2
3
MeuProjeto-Templates> dotnet pack 

MeuProjeto-Templates> dotnet nuget push ...

E a instalação pode ser feita pelo identificador do pacote.

1
 MeuProjeto-Templates> dotnet new --install Empresa.Templates
comments powered by Disqus
Mantido com ❤ por Jéf
Criado com Hugo
Tema Stack desenvolvido por Jimmy