[Avançado] Criando templates customizados em C#
Published: 2022-05-10 15:53:52

> Portanto, se alguém está em Cristo, é nova criação. As coisas antigas já passaram; eis que surgiram coisas novas!
2 Coríntios 5.17

### Sumário:
- [Estrutura final](#estrutura-final)
- [Preparando a solução](#preparando-a-solução)
- [Criando repositório remoto](#criando-repositório-remoto)
- [Github Secrets](#github-secrets)
- [Configurando o pacote](#configurando-o-pacote)
- [Publicando o template](#publicando-o-template)
- [Fontes](#fontes)
- [Agradecimentos](#agradecimentos)

No [tutorial anterior](https://dev.to/vinibeloni/simples-criando-templates-customizados-em-c-2d2j) vimos juntos como criar um template de um jeito bem simples, agora porém, vamos escalar a simplicidade e criar uma solução completa de forma replicável, onde teremos publicação automática utilizando Github Actions, Nuget registry e muito mais!

### Estrutura final 
[voltar ao topo](#sumário)

```shell
PS C:/repos/my-templates> tree /f
│   .gitignore
│   TemplatePack.csproj
│
├───.github
│   └───workflows
│           publish.yml
│
└───templates
    └───customapi
        │   .dockerignore
        │   .editorconfig
        │   .gitattributes
        │   .gitignore
        │   Dockerfile
        │   MyProject.sln
        │
        ├───.template.config
        │       template.json
        │
        ├───src
        │   │   appsettings.Development.json
        │   │   appsettings.json
        │   │   MyProject.csproj
        │   │   Program.cs
        │   │   WeatherForecast.cs
        │   │
        │   ├───Controllers
        │   │       WeatherForecastController.cs
        │   │
        │   └───Properties
        │           launchSettings.json
        │
        └───tests
                MyProject.Tests.csproj
                UnitTest1.cs
```

Vamos começar criando um diretório para hospedar os templates, pois com esse tutorial será possível ter vários em um único pacote. Então para a API vamos criar dois projetos, um para o código de produção(src) e outro para testes de unidade(tests) mas fique a vontade para seguir o que funciona pra você, também vamos criar uma _solution_ e outros arquivos.

## Preparando a solução
[voltar ao topo](#sumário)

```shell
> mkdir templates/customapi
> cd templates/customapi
> dotnet new webapi --no-https -n MyProject -o src
> dotnet new xunit -n MyProject.Tests -o tests
> dotnet new sln -n MyProject
> dotnet add ./tests/ reference ./src
> dotnet sln add ./src/ ./tests/
```

Agora vamos criar o restante dos arquivos (Encoding: UTF-8):

```shell
> '' > Dockerfile
> '' > .editorconfig
> '' > .gitignore
> '' > .dockerignore
> '' > .gitattributes
> mkdir .template.config
> '' > .template.config/template.json
```

`.gitignore`: 

https://github.com/github/gitignore/blob/main/VisualStudio.gitignore

`Dockerfile`:
```dockerfile
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build

# remover se não usar .editorconfig
ENV PATH="$PATH:/root/.dotnet/tools"
RUN dotnet tool install -g dotnet-format

WORKDIR /src
COPY . .

RUN dotnet restore
RUN dotnet build -c Release --no-restore
RUN dotnet test -c Release --no-build

# remover se não usar .editorconfig
RUN dotnet format --verify-no-changes

RUN dotnet publish "src/MyProject.csproj" -c Release --no-build -o /app/publish

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "MyProject.dll"]
```

`.dockerignore`:
```
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
```

`.gitattributes`:
```
* text=auto
*.cs text eol=crlf
```

`.editorconfig` (Opcional):
[Padrão do .NET](https://docs.microsoft.com/pt-br/dotnet/fundamentals/code-analysis/code-style-rule-options)
[O padrão que eu utilizo](https://gist.github.com/vinibeloni/46dc1601f6139c0523faab1276e52778)

Para formatar seu código instale o [dotnet format](https://github.com/dotnet/format) e execute o seguinte comando:

```shell
> dotnet tool install -g dotnet-format
> dotnet format
```

`template.json`: 
Podemos seguir o mesmo do [tutorial anterior](https://dev.to/vinibeloni/simples-criando-templates-customizados-em-c-2d2j):

```json
{
    "$schema": "http://json.schemastore.org/template",
    "author": "@confianodev",
    "identity": "MyCustomWebApi",
    "name": "My Custom Web Api",
    "shortName": "customapi",
    "preferNameDirectory": false,
    "sourceName": "MyProject",
    "classifications": ["Web API", "API"],
    "tags": {
        "language": "C#",
        "type": "project"
    }
}
```

### Criando repositório remoto
[voltar ao topo](#sumário)

Agora começa as diferenças, no caso como estamos trabalhando com multiplos projetos e até templates, vamos criar um arquivo `.csproj` na raiz da nossa solução. Este arquivo terá várias informações para empacotar todos os templates e publicá-los posteriormente, mas antes vamos criar um repositório remoto.

Vá até o [Github](https://github.com/new), crie um novo repositório vazio e siga este passo-a-passo:

```shell
> cd ../..
> git init
> git add .
> git commit -m "initial template files"
> git branch -M main
> git remote add origin https://github.com/SEU-USER/seurepo.git
> git push -u origin main
```

### Github Secrets
[voltar ao topo](#sumário)

Para publicar o pacote devemos criar um Token, basta criar um [Personal Access Token](https://github.com/settings/tokens) com permissões de `write:packages` e copiar o conteúdo, depois vá para a aba 
`Setting > Secrets > Actions` do repositório que criamos e clique em "New repository secret", ao pedir um _Name_ coloque como "PACKAGES_TOKEN" (válido para este tutorial, mas pode colocar o nome que quiser) e no _Value_ cole o Token que você acabou de criar.

### Configurando o pacote
[voltar ao topo](#sumário)

Com tudo isso pronto, vamos criar nosso arquivo de template que será utilizado pelo comando `dotnet pack`. Lembrando que o campo `<RepositoryUrl>` precisa ser preenchido corretamente com o nome do repositório que acabamos de criar, caso contrário teremos problemas ao publicar o nosso pacote:

```shell
> '' > TemplatePack.csproj
```

`TemplatePack.csproj`:

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <PackageType>Template</PackageType>
    <PackageVersion>1.0.0</PackageVersion>
    <PackageId>my.templates</PackageId>
    <Title>My Templates</Title>
    <Authors>@confianodev</Authors>
    <RepositoryUrl>ALÔ, GALERA DE COWBOY</RepositoryUrl>
    <RepositoryType>git</RepositoryType>
    <Description>Templates utilizados para criar meus projetos HAHA</Description>
    <PackageTags>dotnet-new;templates;</PackageTags>
    <TargetFramework>net6.0</TargetFramework>
    <IncludeContentInPack>true</IncludeContentInPack>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <ContentTargetFolders>content</ContentTargetFolders>
    <NoDefaultExcludes>true</NoDefaultExcludes>
    <NoWarn>$(NoWarn);NU5128</NoWarn>
  </PropertyGroup>

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

Para mais informações, acesse [este link](https://docs.microsoft.com/pt-br/dotnet/core/tutorials/cli-templates-create-template-package).

Customize do jeito que for melhor, nesse momento poderiamos aplicar nossos padrões, criar classes de apoio(repositórios, métodos de extensão, configurações de log, startup, etc). Após tudo feito, podemos testar:

```shell
> dotnet test templates/customapi/tests
> dotnet run --project templates/customapi/src
```

Agora vamos empacotar tudo e testar nosso novo template, pois o próximo passo será jogar isso para o Nuget:

```shell
> dotnet pack
> dotnet new -i ./bin/Debug/my.templates.1.0.0.nupkg
> dotnet new customapi -n Pimba -o sample
> rm /sample
> dotnet new --uninstall my.templates
```

Excelente! Template testado e funcional, agora vamos automatizar a publicação de novas versões e publicar o nosso pacote Nuget.

### Publicando o template
[voltar ao topo](#sumário)

Vamos configurar o Github Actions:

```shell
> mkdir .github/workflows
> '' > .github/workflows/publish.yml
```

`publish.yml`:

```yml
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Setup .NET 6 # atualize se for necessário
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 6.0.202 # atualize se for necessário

      - name: Generate package
        run: dotnet pack -c Debug -o out --include-symbols --include-source --verbosity Detailed

      - name: Publish NuGet
        run: |
          cd out
          dotnet nuget push *.nupkg --api-key ${{ secrets.PACKAGES_TOKEN }} --source https://nuget.pkg.github.com/SEU-USER/index.json --skip-duplicate
```

Faça o commit das alterações e vamos ver o resultado do Github Actions:

```shell
> copy ./templates/customapi/.gitignore .gitignore
> git add .
> git commit -m "add publish action"
> git push
```

Se tudo deu certo, na aba _Actions_ do seu reposítorio o pacote estará sendo preparado e pronto para ser utilizado. Mas agora que seu pacote está disponível, precisamos adicioná-lo localmente:

*Observação*: Vou utilizar o Token que criei acima, mas o *ideal* para utilizar pacotes privados é criar um outro Token somente com o acesso `read:packages`.

```shell
> dotnet nuget add source "https://nuget.pkg.github.com/SEU-USER/index.json" -n Github -u "SEU-USER" -p "TOKEN-QUE-CRIAMOS" --store-password-in-clear-text
> dotnet new -i my-templates
> dotnet new customapi -n Opa -o sample
```

Saída do meu terminal:
```shell
PS C:\repos\criando-template> dotnet new -i power.templates
The following template packages will be installed:
   power.templates

Failed to download package 'power.templates.1.0.0' from 'https://nuget.pkg.github.com/confianodev/download/power.templates/1.0.0/power.templates.1.0.0.nupkg'.
The HTTP request to 'GET https://nuget.pkg.github.com/confianodev/download/power.templates/1.0.0/power.templates.1.0.0.nupkg' has timed out after 100000ms.
Warning: Failed to scan C:\repos\criando-template\src.
Details: Template package location C:\repos\criando-template\src is not supported, or doesn't exist.
Success: power.templates::1.0.0 installed the following templates:
Template Name  Short Name  Language  Tags       
-------------  ----------  --------  -----------
My Web Api     customapi   [C#]      Web API/API
```

Para adicionar novos templates, basta criar novas pastas e projetos dentro da pasta `templates` assim como fizemos para a `templates/customapi`:

Exemplo:
```shell
> mkdir templates/customrobot
> cd templates/customrobot
> dotnet new console -n MyProject -o src
```

E é isso! Fico muito feliz de ter compartilhado com você esta experiência. Espero que consiga aumentar a sua produtividade e das pessoas que trabalham com você. Qualquer dúvida por favor deixem nos comentários!

Até a próxima, se assim o Senhor Pai nos permitir, Abraços!

### Fontes
[voltar ao topo](#sumário)

[Como criar um pacote modelo](https://docs.microsoft.com/pt-br/dotnet/core/tutorials/cli-templates-create-template-package)
[Working with the Nuget registry](https://docs.github.com/pt/packages/working-with-a-github-packages-registry/working-with-the-nuget-registry)
[https://docs.microsoft.com/pt-br/nuget/nuget-org/publish-a-package](https://docs.microsoft.com/pt-br/nuget/nuget-org/publish-a-package)

### Agradecimentos
[voltar ao topo](#sumário)

Agradecimentos especiais ao [SammyROCK](https://github.com/SammyROCK), pois trabalhando com ele aprendi muita coisa, principalmente sobre Docker, Github Actions, Secrets, Nuget Registry, etc. Obrigado!