BT

Início Artigos Usando o .Net Core Template Engine para criar templates e projetos personalizados

Usando o .Net Core Template Engine para criar templates e projetos personalizados

Favoritos

Pontos Principais

  • A interface de linha de comando do .NET inclui um mecanismo de template, que pode criar novos projetos e itens diretamente da linha de comando, utilizando o "dotnet new";
  • O conjunto padrão de templates abrange o projeto e os tipos de arquivos essenciais necessários para o console padrão e aplicações baseadas no ASP.NET, e testes de projetos;
  • Os templates personalizados podem criar projetos e itens mais interessantes ou personalizados e podem ser distribuídos e instalados a partir de pacotes NuGet ou diretamente do sistema de arquivos;
  • Os templates personalizados podem ser simples ou mais complexos, com variáveis de substituição, parâmetros de linha de comando e inclusão condicional de arquivos ou mesmo linhas de código;
  • A manutenção e o teste de templates personalizados são fáceis, mesmo com código condicional, garantindo que um template de projeto seja sempre um projeto executável.

Este artigo faz parte da série educacional .NET que explora os benefícios da tecnologia e como pode ajudar não apenas os desenvolvedores .NET tradicionais, mas também todos aqueles que precisam trazer soluções robustas, com desempenho e econômicas ao mercado.

Com o lançamento do .NET Core 3.0, a Microsoft possui a nova versão da plataforma focada no uso geral, modular, multiplataforma e de open source lançada inicialmente em 2016. O .NET Core foi criado inicialmente para permitir a próxima geração das soluções ASP.NET, mas agora direciona e é a base para muitos outros cenários, incluindo IoT, nuvem e soluções móveis. A versão 3 inclui vários recursos frequentemente solicitados, como suporte para WinForms, WPF e Entity Framework 6.

A história das ferramentas mudou drasticamente com o .NET Core, devido a ênfase na linha de comando. Essa é uma excelente opção para a imagem independente de ferramenta e plataforma cruzada do .NET Core. A interface de linha de comando (CLI) do dotnet é o ponto de entrada para toda essa funcionalidade e contém muitos comandos diferentes para criar, editar e empacotar projetos do .NET Core. Neste artigo, focaremos apenas um aspecto do CLI do dotnet, o novo comando dotnet new.

Esse comando é usado principalmente para criar projetos, e normalmente podemos criar um projeto simples e, em seguida, esquecer que o utilizamos. Vamos ver como tirar o máximo dele, transmitindo argumentos para modificar os projetos gerados e vendo como podemos usar o comando para criar arquivos e projetos. Também veremos que essa ferramenta é um mecanismo de template completo e pode ser usada para instalar templates personalizados, inclusive para criar templates pessoais.

dotnet new em ação

Então, como usaremos o dotnet new? Vamos começar trabalhando com as coisas mais interessantes. Para criar uma aplicação de console simples, iniciemos a interface de linha de comando, alteremos o diretório para uma pasta vazia (uma etapa importante, que vamos explicar no desenrolar do artigo) e chamemos o dotnet new console:

> dotnet new console
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on /Users/matt/demo/MyNewApp/MyNewApp.csproj...
  Restoring packages for /Users/matt/demo/MyNewApp/MyNewApp.csproj...
  Generating MSBuild file /Users/matt/demo/MyNewApp/obj/MyNewApp.csproj.nuget.g.props.
  Generating MSBuild file /Users/matt/demo/MyNewApp/obj/MyNewApp.csproj.nuget.g.targets.
  Restore completed in 234.92 ms for /Users/matt/demo/MyNewApp/MyNewApp.csproj.
Restore succeeded.

Como mencionei antes, precisamos verificar se estamos dentro de uma pasta vazia primeiro. Por padrão, o dotnet new criará arquivos na pasta atual e não excluirá nada que esteja lá. Podemos criar uma nova pasta usando a opção --output. Por exemplo, podemos criar um projeto em uma nova pasta chamada ConsoleApp42 digitando:

> dotnet new console --output ConsoleApp42
The template "Console Application" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on ConsoleApp42/ConsoleApp42.csproj...
  Restoring packages for /Users/matt/demo/ConsoleApp42/ConsoleApp42.csproj...
  Generating MSBuild file /Users/matt/demo/ConsoleApp42/obj/ConsoleApp42.csproj.nuget.g.props.
  Generating MSBuild file /Users/matt/demo/ConsoleApp42/obj/ConsoleApp42.csproj.nuget.g.targets.
  Restore completed in 309.99 ms for /Users/matt/demo/ConsoleApp42/ConsoleApp42.csproj.
Restore succeeded.

Dando uma olhada no que criamos

Neste ponto, o dotnet new criou um novo projeto através do console e restaurou os pacotes NuGet, então está tudo pronto para ser executado. Mas vamos dar uma olhada no que foi criado:

> ls ConsoleApp42/
ConsoleApp42.csproj  Program.cs obj/

Como podemos ver, agora temos um arquivo do projeto com base no nome da pasta de saída. Se desejarmos, podemos usar o parâmetro --name para especificar um nome diferente:

dotnet new console --output ConsoleApp42 --name MyNewApp

Isso iria criar os arquivos do projeto em uma pasta chamada ConsoleApp42 e usaria MyNewApp como o nome da aplicação de console que está sendo criada, obteríamos o arquivo MyNewApp.csproj. Se dermos uma olhada no Program.cs, também veremos que o parâmetro name é usado para atualizar o namespace:

using System;
namespace ConsoleApp42
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Se preparando para outro projeto

Mas, se olharmos a estrutura de pastas do projeto que acabamos de criar, perceberemos que algo está faltando. Não há arquivo de solução. Temos apenas um projeto e, embora funcione bem com a execução do dotnet, teremos problemas quando desejarmos adicionar outro projeto. Podemos criar facilmente um com o comando:

dotnet new sln

Isso criará um novo arquivo de solução vazio. Então, será outro passo adicionar um projeto a ele.

Se quisermos criar a solução na pasta raiz da demo, precisamos usar o seguinte comando:

dotnet sln add ConsoleApp42/MyApp.sln

Também podemos usar o comando dotnet sln para remover ou listar projetos em uma solução. Se desejarmos adicionar ou remover as referências de um projeto, podemos usar o comando dotnet add. Sugiro ler o artigo de Jeremy Miller na CLI extensível do dotnet para obter mais detalhes ou digitar dotnet help sln ou dotnet help add.

Adicionar outro projeto também é muito fácil, mas devemos fazê-lo em duas etapas, primeiro criando e depois adicionando. Por exemplo, poderíamos adicionar um projeto de teste à solução:

dotnet new nunit --output Tests --name MyAppTests
dotnet sln add Tests/MyAppTests.csproj

Adicionando novos arquivos a um projeto

Adicionar novos arquivos a um projeto é ainda mais fácil, principalmente graças às melhorias que o .NET Core fez nos arquivos do MSBuild. Não precisamos mais listar explicitamente os arquivos C# no arquivo .csproj, porque são automaticamente capturados por marcadores. Só precisamos criar um arquivo na pasta e ele se tornará automaticamente parte do projeto. Podemos criar o arquivo manualmente ou usar o dotnet new para fornecer um arquivo de template. Por exemplo, adicionamos um arquivo de teste ao projeto utilizando o template de item nunit-test:

dotnet new nunit-test --output Tests --name MyNewTests

Falando em templates, como sabemos quais estão disponíveis? Como podemos dizer a diferença entre um template de projeto e um template de um item? Esse é um trabalho do dotnet new --list, que exibe uma lista de templates disponíveis:

Templates Nome Abreviado Linguagem Tags
Console Application console [C#], F#, VB Common/Console
Class library classlib [C#], F#, VB Common/Library
Unit Test Project mstest  [C#], F#, VB Test/MSTest
NUnit 3 Test Project nunit [C#], F#, VB Test/NUnit
NUnit 3 Test Item nunit-test [C#], F#, VB Test/NUnit
xUnit Test Project xunit [C#], F#, VB Test/xUnit
Razor Page page [C#] Web/ASP.NET
MVC ViewImports viewimports [C#] Web/ASP.NET
MVC ViewStart viewstart [C#] Web/ASP.NET
ASP.NET Core Empty web [C#], F# Web/Empty
ASP.NET Core Web App (Model-View-Controller) mvc [C#], F# Web/MVC
ASP.NET Core Web App razor [C#] Web/MVC/Razor Pages
ASP.NET Core with Angular angular [C#] Web/MVC/SPA
ASP.NET Core with React.js react [C#] Web/MVC/SPA
ASP.NET Core with React.js and Redux reactredux [C#] Web/MVC/SPA
Razor Class Library razorclasslib [C#] Web/Razor/Library/Razor Class Library
ASP.NET Core Web API webapi [C#], F# Web/WebAPI
global.json file globaljson   Config
NuGet Config nugetconfig   Config
Web Config webconfig   Config
Solution File sln   Solution

Essa opção lista todos os templates. Podemos usar o parâmetro --type para filtrar, usando --type project, --type item ou --type other. Os templates de projeto irão criar um projeto, os templates de item criarão um único arquivo, enquanto outros são realmente úteis apenas para o template sln criar um arquivo de solução.

O nome abreviado (segunda coluna acima) da lista é o nome que usamos na chamada para o dotnet new (por exemplo, dotnet new console, dotnet new classlib, dotnet new mvc, etc.). Alguns templates suportam várias linguagens, com o padrão mostrado entre colchetes (spoiler, é sempre C#). Podemos escolher uma linguagem diferente com a opção --language, mas tenha cuidado com o símbolo #! Alguns shells da linha de comando tratam isso como um caractere de comentário e a análise pode falhar com --language F#. Podemos tratar isso colocando entre aspas, "--language F#".

Por fim, cada template possui uma ou mais tags. Essa é uma maneira de classificar os templates, mas atualmente não é usada como parte das ferramentas de linha de comando. No entanto, podem ser usados para agrupar ou filtrar por outros hosts. Sim, isso mesmo, o novo mecanismo de template dotnet pode ser usado em outros hosts, como IDEs. Falaremos sobre isso mais tarde.

Customizando os templates

Até agora, vimos uma aplicação simples do console, semelhante ao Hello World. Vejamos algo mais interessante. Digamos que queremos criar um novo projeto ASP.NET. Olhando para a lista de templates acima, temos algumas opções. Podemos criar um projeto web vazio, um projeto MVC, um projeto com Angular ou um com React.js. Mas esses são templates bastante inflexíveis. Podemos personalizá-los? A boa notícia é que, sim, podemos.

Os templates podem receber parâmetros que alteram o que está sendo gerado. O comando --help fornecerá detalhes sobre os parâmetros que este template entende. Vamos começar com um exemplo simples:

> dotnet new classlib --help
Class library (C#)
Author: Microsoft
Description: A project for creating a class library that targets .NET Standard or .NET Core
Options:
-f|--framework  The target framework for the project.
                      netcoreapp2.1     - Target netcoreapp2.1
                      netstandard2.0    - Target netstandard2.0
                  Default: netstandard2.0
 
  --no-restore    If specified, skips the automatic restore of the project on create.
                  bool - Optional
                  Default: false / (*) true
* Indicates the value used if the switch is provided without a value.

Aqui podemos observar que o template classlib possui dois parâmetros: --framework para especificar qual estrutura de destino é gravada no arquivo do projeto, e --no-restore, para controlar se a restauração do NuGet é executada quando o projeto é criado.

dotnet new classlib --framework netcoreapp2.1 --no-restore

Os templates web têm parâmetros semelhantes, mas há tantos que não temos espaço para listar neste artigo. Vamos experimentar o dotnet new mvc --help para ter uma idéia do que está disponível. Existem parâmetros para decidir que tipo de autenticação desejamos, se queremos desabilitar o HTTPS ou não, usar o LocalDB em vez do SQLite e assim por diante. Cada um desses parâmetros altera a maneira como o código do template é gerado, substituindo o conteúdo dos arquivos ou incluindo e/ou excluindo arquivos, conforme for mais apropriado.

Enquanto falamos de ajuda, aqui estão dois comandos muito úteis: dotnet help new, que abre uma página web no próprio dotnet new; e dotnet new {template} -help, que mostra a ajuda para o template nomeado e os parâmetros que pode receber.

Adicionando templates customizados

O poder real do novo comando dotnet é a capacidade de adicionar novos templates personalizados. Melhor ainda, os templates podem ser distribuídos e compartilhados, simplesmente colocando-os em um pacote NuGet e enviando para o nuget.org. Isso facilita muito a introdução de uma estrutura ou automatiza o padrão da criação de novos projetos ou itens de projeto.

Para adicionar um novo template personalizado, temos o comando dotnet new --install {template}, passando o nome de um pacote NuGet ou uma pasta de arquivo para um template local. Mas como encontramos os novos templates?

Uma forma é procurar a estrutura que estamos usando e ver se há templates disponíveis, mas isso é um pouco aleatório. Felizmente, podemos visitar dotnetnew.azurewebsites.net e procurar os templates por palavras-chave. Existem mais de 500 templates rastreados no site, o que torna-o um excelente local para fazermos novas descobertas.

Por exemplo, podemos instalar um conjunto de templates para projetos do AWS Lambda com o dotnet new --install Amazon.Lambda.Templates. Uma característica muito boa da instalação de templates via pacotes NuGet é que cada pacote pode conter mais de um template. Este pacote do AWS Lambda contém 28 templates diferentes, incluindo um projeto tutorial.

Obviamente, se não quisermos mais usar o template, podemos desinstalá-lo com o dotnet new --uninstall {package}. O nome passado aqui é o nome do pacote de templates instalado e, se não tivermos certeza do nome, podemos executar o dotnet new --uninstall para obter a lista.

Criando nossos próprios templates

Podemos ainda, criar templates próprios e personalizados. Isso não precisa ser feito para frameworks populares, mas pode ser desenvolvido para projetos internos ou pessoais. Essencialmente, se costumamos criar uma estrutura de pastas, um conjunto de referências ou arquivos padrão, pode ser interessante considerarmos a criação de templates de projetos ou itens. Os templates de projeto são simplesmente arquivos de texto sem formatação, incluindo os arquivos .csproj, não é necessário que os templates gerados sejam específicos do .NET Core, podendo ser criados para atingir qualquer tipo de estrutura.

É surpreendentemente fácil criar e manter um novo template. Tradicionalmente, os templates que podem executar a substituição de texto usam uma sintaxe especial, como os marcadores $VARIABLE$, que serão substituídos quando o template for avaliado. Infelizmente, isso geralmente é uma sintaxe inválida para o tipo de arquivo, o que impossibilita a execução do projeto para testar se o template está correto. Isso leva a erros e tempos de iteração lentos e, basicamente, um pouco de dor de cabeça na manutenção.

Felizmente, os projetistas do mecanismo de template resolveram este problema e criaram uma maneira muito mais agradável de trabalhar executando os templates.

A ideia é simples, o template é apenas arquivos de texto simples. Sem formatos especiais, sem marcadores especiais. Portanto, um arquivo C# é sempre um arquivo C# válido. Se um template deseja substituir algum texto, como substituir um namespace no C# por outro, baseado no nome do projeto, isso é tratado com uma pesquisa e substituição simples. Por exemplo, imagine que tivéssemos um template assim:

namespace RootNamespace
{
  public static class Main
  {
    // ...
  }
}

A configuração JSON do template define um símbolo que substituirá o espaço para o nome. O valor do símbolo seria baseado no nome do projeto, possivelmente com uma transformação interna aplicada para garantir que contenha apenas caracteres válidos. O símbolo também definiria o texto que estava substituindo, "RootNamespace" no caso. Quando o mecanismo de template processa cada arquivo, se houver um "RootNamespace", será substituído pelo valor do símbolo.

Essa pesquisa e substituição simples, geralmente consiste em um símbolo baseado em um parâmetro, como o nome do template, o nome da saída ou um parâmetro personalizado. Também é possível criar símbolos com base em geradores, criar GUIDs, números aleatórios ou o carimbo de data e hora atual e assim por diante.

Porém, nenhum template é completo sem um código condicional, algo que é adicionado ou removido com base em um parâmetro. Como o dotnet new lida com isso e mantém os "templates em execução" como uma opção? Na verdade, isso é tratado com base no tipo de arquivo, com algumas configurações padrão integradas e a capacidade de definir o próprio estilo para formatos de arquivo desconhecidos. Essencialmente, a ideia é usar o pré-processador específico do arquivo (como #if para C# ou C++) para os tipos de arquivos que o suportam, e comentários formatados especialmente para aqueles que não suportam, como JSON.

```cs
public class HomeController : Controller
{
  public IActionResult Index() => View();
 
  public IActionResult About()
  {
    ViewData["Message"] = "Your application description page.";
    return View();
  }
 
#if (EnableContactPage)
  public IActionResult Contact()
  {
    ViewData["Message"] = "Your contact page.";
    return View();
  }
#endif
 
  public IActionResult Error() => View();
}
```

Todos os metadados de um template estão em um arquivo template.json. Isso inclui o nome abreviado do template, descrição, autor, tags e linguagem suportada. Como um template pode ser aplicado a apenas uma linguagem, o arquivo .json também inclui uma opção de "identidade de grupo", que vários templates podem especificar, um para cada linguagem. O arquivo de metadados também pode incluir informações opcionais sobre a origem e o destino dos arquivos a serem copiados ou renomeados, cópias condicionais de arquivos, símbolos de substituição, parâmetros de linha de comando e ações pós-criação, como restauração de pacotes. Porém, por padrão, o mecanismo do template copiará e processará todos os arquivos na estrutura de arquivos do template.

{
  "author": "Matt Ellis",
  "classifications": [ "Hello world" ],
  "name": "Hello world template",
  "identity": "Matt.HelloWorldTemplate.CSharp",
  "shortName": "helloworld",
  "guids": [ "d23e3131-49a0-4930-9870-695e3569f8e6" ],
  "sourceName": "MyTemplate"
}

O template.json deve ser colocado na raiz da estrutura de pastas do template, em uma pasta chamada .template.config. O restante da estrutura de pastas é inteiramente decidido por nós, o mecanismo do template manterá a mesma estrutura de pastas ao avaliá-lo. Em outras palavras, se adicionarmos um arquivo README.md à raiz da estrutura de pastas do nosso template, o mecanismo de template criará um README.md na raiz da pasta de saída quando chamarmos o dotnet new. Portanto, se usarmos --output MyApp, receberemos um arquivo chamado MyApp/README.md.

> tree -a
.
├── .template.config
│   └── template.json
├── MyTemplate.csproj
├── Program.cs
└── Properties
    └── AssemblyInfo.cs

2 pastas, 4 arquivos.

Para instalar e testar nosso template, basta chamar dotnet new --install {template} como faríamos para instalar um template personalizado, mas desta vez, passe o caminho para a raiz da estrutura de pastas do template. Para desinstalar, use o dotnet new --uninstall {template}. Novamente, se não tivermos certeza do que deve passar, use `dotnet new --uninstall` para obter uma lista completa.

Distribuindo nossos templates

Quando estiver pronto para distribuir o template, podemos empacotá-lo em um pacote NuGet e fazer o upload para nuget.org. Precisaremos criar um arquivo .nuspec normalmente, mas com dois pequenos ajustes, primeiro adicionando um elemento packageType e definindo o atributo name como "Template", depois verificando se a estrutura da pasta do template é copiada para uma pasta chamada "content":

<package>
  <metadata>
    <id>MattDemo.HelloWorldTemplate</id>
    <version>1.0</version>
    <authors>Matt Ellis</authors>
    <description>Hello World template</description>
    <packageTypes>
      <packageTypename="Template"/>
    </packageTypes>
  </metadata>
  <files>
    <filesrc=".template.config/template.json"target="content/.template.config"/>
    <filesrc="MyTemplate.csproj"target="content/"/>
    <filesrc="Program.cs"target="content/"/>
    <filesrc="Properties/*"target="content/Properties/"/>
  </files>
</package>

Além disso, é possível incluir vários templates em um único pacote, basta criarmos várias pastas em "content" e adicionar a.tempate.config/template.json para cada template.

Há muito mais opções e recursos no arquivo template.json, mas a cobertura de todas elas está além do escopo deste artigo. Todavia, com base em tudo o que abordamos aqui, podemos observar que o mecanismo do template é muito poderoso, flexível e, no entanto, bastante simples de usar. Confira o site de documentos da Microsoft e o wiki do site do GitHub dotnet/templates.

O mecanismo do template

Uma das coisas mais interessantes sobre o dotnet new é ter sido projetado para ser usado em vários hosts. A ferramenta dotnet new do CLI é simplesmente um host, o próprio mecanismo do template pode ser usado como uma API de outras aplicações. Isso é ótimo para aqueles que preferem trabalhar com uma IDE em vez da linha de comando, mas ainda querem poder adicionar facilmente templates de projeto personalizados, algo que nem sempre é fácil com uma IDE.

Podemos ver isso em ação no JetBrains Rider. A caixa de diálogo New Project é desenvolvida pelas APIs do mecanismo de template, listando todos os templates disponíveis, mesmo que estes sejam personalizados. Quando o usuário deseja criar um projeto, o mecanismo de template é usado para gerar os arquivos.

Se olharmos atentamente, veremos que o Rider tem mais templates que o CLI do .NET. Isso ocorre porque o Rider envia templates extras para dar suporte aos projetos do .NET Framework e Xamarin. A API do mecanismo do template permite que os hosts instalem templates em um local personalizado e podem listá-los de ambos, o que significa que o Rider mostrará templates personalizados instalados pelo dotnet new --install, além de usar o botão Install na caixa de diálogo "More templates". Uma vez recarregado, o novo template é mostrado na lista, assim como todos os outros.

Projetos novos e personalizados com facilidade

O comando dotnet new facilita a criação de novos projetos e itens de projeto. O conjunto padrão de templates nos ajudará a criar aplicações .NET Core, com linha de comando ou com base no ASP.NET, e ajudará a criar projetos de teste e a direcionar outras linguagens .NET. Templates personalizados podem ser facilmente instalados para criar projetos com outros requisitos, como uma estrutura de pastas ou dependências de framework diferentes. E o formato para templates personalizados facilita a criação dos nossos próprios, aproveitando as variáveis de substituição e o código condicional, mas mantendo o projeto do template compilável e sustentável. Juntamente com o comando dotnet sln, bem como outros comandos extensíveis do CLI dotnet, o novo mecanismo de template facilita a criação e o gerenciamento de projetos, itens e soluções, de forma consistente entre plataformas e diretamente da linha de comando.

Sobre o autor

Matt Ellis é um entusiasta e desenvolvedor no JetBrains. Passou mais de 20 anos entregando software em vários setores e atualmente trabalha com IDEs e ferramentas de desenvolvimento, se divertindo com árvores de sintaxe abstratas e análise de código-fonte. Também trabalha no suporte da Unity em Rider.

Avalie esse artigo

Relevância
Estilo/Redação

Olá visitante

Você precisa cadastrar-se no InfoQ Brasil ou para enviar comentários. Há muitas vantagens em se cadastrar.

Obtenha o máximo da experiência do InfoQ Brasil.

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

Comentários da comunidade

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

HTML é permitido: a,b,br,blockquote,i,li,pre,u,ul,p

BT

Seu cadastro no InfoQ está atualizado? Poderia rever suas informações?

Nota: se você alterar seu email, receberá uma mensagem de confirmação

Nome da empresa:
Cargo/papel na empresa:
Tamanho da empresa:
País:
Estado:
Você vai receber um email para validação do novo endereço. Esta janela pop-up fechará em instantes.