Pontos Principais
- O dotnet cli torna a automação e a criação de scripts em projetos .NET simples, especialmente em comparação ao estado da arte no .NET há uma década ou mais.
- O modelo de extensibilidade dotnet cli facilita a incorporação de aplicativos de linha de comando externos criados pelo .NET, distribuídos via Nuget em suas construções automatizadas.
- O dotnet cli permite a execução de testes em seus scripts de construção para suas soluções.
- A saída de teste para o dotnet cli ajuda com um melhor uso da integração contínua (CI).
- Usando tecnologias de contêiner como o Docker é muito mais fácil usando o dotnet cli.
Com o lançamento do .NET Core 2.0, a Microsoft tem a próxima versão principal da plataforma de propósito geral, modular, multiplataforma e de código aberto que foi lançada inicialmente em 2016. O .NET Core foi criado para ter muitas das APIs que estão disponíveis na versão atual do .NET Framework. Ele foi inicialmente criado para permitir a próxima geração de soluções ASP.NET, mas agora impulsiona e é a base para muitos outros cenários, incluindo IoT, nuvem e soluções móveis de próxima geração. Nesta segunda série, abrangendo o .NET Core, exploraremos mais alguns benefícios do .NET Core e como ele pode beneficiar não apenas desenvolvedores tradicionais de .NET, mas também todos os tecnólogos que precisam levar soluções robustas, eficazes e econômicas ao mercado.
Este artigo do InfoQ faz parte da série ".NET Core". Você pode se inscrever para receber notificações via RSS.
Recentemente, perguntaram-me quais são as vantagens de migrar para o .NET Core de pessoas que hesitaram ou não conseguiram desligar o .NET framework antigo e completo. Como resposta, foi mencionado o melhor desempenho, o formato de arquivo csproj aprimorado, a capacidade de teste aprimorada do ASP.NET Core, que é a plataforma cruzada.
Como o autor de várias ferramentas de OSS (Marten, StructureMap e Alba é referenciado neste projeto como exemplos), a maior vantagem para mim, pessoalmente, possivelmente foi o advento do cli do dotnet. Usada em conjunto com o novo formato de arquivo csproj do .NET SDK, a ferramenta dotnet cli tornou muito mais fácil para mim criar e manter scripts de construção para meus projetos. É mais fácil executar testes em scripts de compilação, mais fácil consumir e publicar pacotes Nuget, e o mecanismo de extensibilidade cli é fantástico para incorporar executáveis personalizados distribuídos por pacotes Nuget em compilações automatizadas.
Para começar com o dotnet cli, primeiro instale o .NET SDK em sua máquina de desenvolvimento. Feito isso, há algumas coisas úteis para lembrar:
- As ferramentas "dotnet" são instaladas globalmente em seu PATH e estão disponíveis em qualquer lugar em seus prompts de linha de comando
- O dotnet cli usa o estilo Linux da sintaxe de comando usando "--word [value]" para flags opcionais em longhand ou "-w [value]" como uma abreviação. Se você está acostumado com as ferramentas de linha de comando Git ou Node.js, você se sentirá em casa com o dotnet cli
- "Dotnet --help" listará os comandos instalados e algum uso básico de sintaxe
- "Dotnet --info" informará qual versão do dotnet cli você está usando. Provavelmente, é uma boa ideia chamar esse comando de integração contínua em sua integração para solucionar problemas mais tarde quando algo funciona localmente e falha no servidor de compilação ou vice-versa
- Embora eu esteja falando sobre o .NET Core neste artigo, observe que você pode usar o novo formato de projeto do SDK e o dotnet cli com versões anteriores do .NET Framework completo
Hello World da linha de comando
Para fazer um tour guiado através de alguns dos destaques do dotnet cli, digamos que queremos construir um simples aplicativo "Hello, World" ASP.NET Core. Apenas por diversão, vamos adicionar algumas reviravoltas:
- Haverá um teste automatizado para nosso serviço da web em um projeto separado
- Implantaremos nosso serviço por meio de um contêiner do Docker, porque é isso que todos os garotos legais fazem (e isso mostra mais detalhes sobre o dotnet cli)
- E, claro, vamos tentar utilizar o dotnet cli tanto quanto possível
Se você quiser ver o produto final deste código, confira este repositório GitHub.
Primeiramente, vamos começar com um diretório vazio chamado "DotNetCliArticle" e abrir sua ferramenta de linha de comando favorita para esse diretório. Vamos começar usando o comando "dotnet new" para gerar um arquivo de solução e novos projetos. O SDK do .NET vem com vários modelos comuns para criar tipos de projetos ou arquivos comuns, com outros modelos disponíveis como complementos (mais sobre isso em uma seção posterior). Para ver quais modelos estão disponíveis em sua máquina, comece usando este comando dotnet new -help, que deve fornecer alguma saída como esta:
Como você notará acima, um dos modelos disponíveis é "sln" para um arquivo de solução vazio. Usaremos esse modelo para começar digitando o comando dotnet new sln
, que gerará essa saída:
The template "Solution File" was created successfully.
Por padrão, esse comando nomeará o arquivo de solução após o diretório contido. Como chamei meu diretório-raiz de "DotNetCliArticle", o arquivo de solução gerado é "DotNetCliArticle.sln".
Indo mais longe, vamos adicionar o projeto atual para o nosso "Hello, World" com este comando:
dotnet new webapi --output HeyWorld
O comando acima executa o template "webapi" para o diretório "HeyWorld" que especificamos através do flag opcional "output". Esse modelo gerará uma estrutura de projeto de núcleo MVC simplificada adequada para APIs headless. Novamente, o comportamento padrão é nomear o arquivo de projeto após o diretório de contenção, portanto, obtemos um arquivo chamado "HeyWorld.csproj" nesse diretório, juntamente com todos os arquivos básicos para um projeto mínimo da ASP.NET MVC Core API. O modelo também configura todas as referências Nuget necessárias ao ASP.NET Core que precisamos em nosso novo projeto para começar.
Desde que eu acabei de construir isso em um pequeno repositório Git, depois de adicionar novos arquivos com git add .
, eu uso git status
para ver o que foi criado recentemente:
new file: HeyWorld/Controllers/ValuesController.cs
new file: HeyWorld/HeyWorld.csproj
new file: HeyWorld/Program.cs
new file: HeyWorld/Startup.cs
new file: HeyWorld/appsettings.Development.json
new file: HeyWorld/appsettings.json
Agora, para adicionar o novo projeto ao nosso arquivo de solução vazio, você pode usar o comando “dotnet sln” como este:
dotnet sln DotNetCliArticle.sln add HeyWorld/HeyWorld.csproj
Tudo bem, agora temos o shell de um novo serviço ASP.NET Core API sem precisar abrir o Visual Studio.NET (ou o JetBrains Rider no meu caso). Para ir um pouco mais longe e começar nosso projeto de teste antes de escrevermos qualquer código real, eu emito estes comandos:
dotnet new xunit --output HeyWorld.Tests
dotnet sln DotNetCliArticle.sln add HeyWorld.Tests/HeyWorld.Tests.csproj
Os comandos acima criam um novo projeto usando xUnit.NET como a biblioteca de teste e adicionam esse novo projeto ao nosso arquivo de solução. O projeto de teste precisa de um projeto de referência para o projeto “HeyWorld” e, felizmente, podemos adicionar uma referência de projeto com a ferramenta bacana “dotnet add” da seguinte forma:
dotnet add HeyWorld.Tests/HeyWorld.Tests.csproj reference HeyWorld/HeyWorld.csproj
Antes de abrir a solução, sei de antemão que há outras referências Nuget que gostaria de usar no projeto de teste. Shouldly é minha ferramenta de asserção de escolha, então adicionarei uma referência à versão mais recente do Shouldly emitindo outra chamada para a linha de comando:
dotnet add HeyWorld.Tests/HeyWorld.Tests.csproj package Shouldly
Que vai me dar alguma saída de linha de comando como esta:
info : Adding PackageReference for package 'Shouldly' into project 'HeyWorld.Tests/HeyWorld.Tests.csproj'.
log : Restoring packages for /Users/jeremydmiller/code/DotNetCliArticle/HeyWorld.Tests/HeyWorld.Tests.csproj...
info : GET https://api.nuget.org/v3-flatcontainer/shouldly/index.json
info : OK https://api.nuget.org/v3-flatcontainer/shouldly/index.json 109ms
info : Package 'Shouldly' is compatible with all the specified frameworks in project 'HeyWorld.Tests/HeyWorld.Tests.csproj'.
info : PackageReference for package 'Shouldly' version '3.0.0' added to file '/Users/jeremydmiller/code/DotNetCliArticle/HeyWorld.Tests/HeyWorld.Tests.csproj'.
Em seguida, desejo adicionar pelo menos mais uma referência do Nuget ao projeto de teste chamado Alba.AspNetCore2, que usarei para criar testes de contrato HTTP com o novo aplicativo da web:
dotnet add HeyWorld.Tests/HeyWorld.Tests.csproj package Alba.AspNetCore2
Agora, só para verificar as coisas um pouco antes de trabalhar com o código, certifico-me de que tudo seja compilado corretamente, emitindo este comando para criar todos os projetos em nossa solução na linha de comando:
dotnet build DotNetCliArticle.sln
E isso não foi compilado devido a um conflito de versões de dependência de diamantes entre o Alba.AspNetCore2 e as versões das referências do ASP.NET Core Nuget no projeto HeyWorld. Não se preocupe, porque isso é facilmente resolvido corrigindo a dependência de versão do Nuget
Microsoft.AspNetCore.All
no projeto de teste da seguinte forma:
dotnet add HeyWorld.Tests/HeyWorld.Tests.csproj package Microsoft.AspNetCore.All --version 2.1.2
No exemplo acima, usar o sinalizador "--version" com o valor "2.1.2" corrigirá a referência exatamente a essa versão, em vez de usar apenas a versão mais recente encontrada nos seus feeds Nuget.
Para verificar novamente se todos os nossos problemas de dependência do Nuget desapareceram, podemos usar os comandos mostrados abaixo para fazer uma verificação mais rápida do que recompilar tudo:
dotnet clean && dotnet restore DotNetCliArticle.sln
Como um desenvolvedor .NET experiente, sou paranóico sobre arquivos remanescentes nas pastas temporárias /obj e /bin. Por causa disso, eu uso o comando "Clean Solution" no Visual Studio.NET sempre que tento alterar as referências apenas no caso de algo ser deixado para trás. O comando "dotnet clean" faz exatamente a mesma coisa em uma linha de comando.
Da mesma forma, o comando "dotnet restore" tentará resolver todas as dependências Nuget conhecidas do arquivo de solução especificado. Nesse caso, usar "restauração de dotnet" nos permitirá identificar rapidamente possíveis conflitos ou perder referências Nuget sem ter que fazer uma compilação completa - e essa é a maneira principal de usar esse comando em meu próprio trabalho. Nas versões mais recentes do dotnet cli, a resolução Nuget é feita automaticamente para você (esse comportamento pode ser substituído com um sinalizador) em chamadas para "dotnet build/test/pack/etc" que exigiriam o Nugets primeiro.
Nossa chamada para "dotnet restore DotNetCliArticle.sln" foi executada sem erros, então estamos finalmente prontos para escrever algum código. Vamos abrir o editor C# de sua escolha e adicionar um arquivo de código ao projeto HeyWorld.Tests que contém um teste de contrato HTTP muito simples que especificará o comportamento que queremos da rota "GET: /" em nosso novo aplicativo HeyWorld:
using System.Threading.Tasks;
using Alba;
using Xunit;
namespace HeyWorld.Tests
{
public class verify_the_endpoint
{
[Fact]
public async Task check_it_out()
{
using (var system = SystemUnderTest.ForStartup<Startup>())
{
await system.Scenario(s =>
{
s.Get.Url("/");
s.ContentShouldBe("Hey, world.");
s.ContentTypeShouldBe("text/plain; charset=utf-8");
});
}
}
}
}
O arquivo resultante deve ser salvo no diretório HeyWorld.Tests com um nome apropriado, como verify_the_endpoints.cs
.
Sem entrar muito na mecânica de Alba, isso apenas especifica que a rota de casa de nosso novo aplicativo HeyWorld deve escrever texto dizendo "Ei, mundo". Nós não codificamos nada de real em nosso aplicativo HeyWorld, mas ainda vamos executar este teste para ver se está conectado corretamente e falha pelo "motivo certo".
De volta na linha de comando, posso executar todos os testes no projeto de teste com este comando:
dotnet test HeyWorld.Tests/HeyWorld.Tests.csproj
Que com nosso único teste que falhará porque nada foi realmente implementado ainda, nos dá esta saída:
Build started, please wait...
Build completed.
Test run for /Users/jeremydmiller/code/DotNetCliArticle/HeyWorld.Tests/bin/Debug/netcoreapp2.1/HeyWorld.Tests.dll(.NETCoreApp,Version=v2.1)
Microsoft (R) Test Execution Command Line Tool Version 15.7.0
Copyright (c) Microsoft Corporation. All rights reserved.
Starting test execution, please wait...
[xUnit.net 00:00:01.8266290] HeyWorld.Tests.verify_the_endpoint.check_it_out [FAIL]
Failed HeyWorld.Tests.verify_the_endpoint.check_it_out
Error Message:
Alba.ScenarioAssertionException : Expected status code 200, but was 404
Expected the content to be 'Hey, world.'
Expected a single header value of 'content-type'='text/plain', but no values were found on the response
Stack Trace:
at Alba.Scenario.RunAssertions()
at Alba.SystemUnderTestExtensions.Scenario(ISystemUnderTest system, Action`1 configure)
at Alba.SystemUnderTestExtensions.Scenario(ISystemUnderTest system, Action`1 configure)
at HeyWorld.Tests.verify_the_endpoint.check_it_out() in /Users/jeremydmiller/code/DotNetCliArticle/HeyWorld.Tests/verify_the_endpoint.cs:line 14
--- End of stack trace from previous location where exception was thrown ---
Total tests: 1. Passed: 0. Failed: 1. Skipped: 0.
Test Run Failed.
Test execution time: 2.5446 Seconds
Para resumir essa saída, um teste foi executado e falhou. Também vemos a saída xUnit padrão que nos fornece algumas informações sobre o motivo do teste ter falhado. É importante observar aqui que o comando "dotnet test" retornará um código de saída com zero denotando sucesso se todos os testes forem aprovados e um código de saída diferente de zero denotando falha se algum dos testes falhar. Isso é importante para scripts de integração contínua (CI) em que a maioria das ferramentas de IC usa o código de saída de todos os comandos para saber quando a construção falhou.
Argumentarei que o teste acima falhou pelo "motivo certo", o que significa que o equipamento de teste parece ser capaz de inicializar o aplicativo real e eu esperava uma resposta 404 porque nada foi codificado ainda. Seguindo em frente, vamos implementar um endpoint principal do MVC para o comportamento esperado:
public class HomeController : Controller
{
[HttpGet("/")]
public string SayHey()
{
return "Hey, world!";
}
}
(Observe que o código anterior deve ser anexado como uma classe adicional em seu arquivo HeyWorld\startup.cs.)
Voltando à linha de comando novamente, vamos executar o comando "dotnet test HeyWorld.Tests/HeyWorld.Tests.csproj"
novamente e esperamos ver resultados como este:
Build started, please wait...
Build completed.
Test run for /Users/jeremydmiller/code/DotNetCliArticle/HeyWorld.Tests/bin/Debug/netcoreapp2.1/HeyWorld.Tests.dll(.NETCoreApp,Version=v2.1)
Microsoft (R) Test Execution Command Line Tool Version 15.7.0
Copyright (c) Microsoft Corporation. All rights reserved.
Starting test execution, please wait...
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 2.4565 Seconds
Tudo bem, agora que o teste foi aprovado, vamos executar o aplicativo real. Como o modelo "dotnet new webapi" usa o servidor web Kestrel em processo para manipular solicitações HTTP, literalmente, a única coisa que precisamos fazer para executar nosso novo aplicativo HeyWorld é iniciá-lo a partir da linha de comando com este comando:
dotnet run --project HeyWorld/HeyWorld.csproj
Executar o comando acima deve fornecer uma saída assim:
Using launch settings from HeyWorld/Properties/launchSettings.json...
: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
User profile is available. Using '/Users/jeremydmiller/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
Hosting environment: Development
Content root path: /Users/jeremydmiller/code/DotNetCliArticle/HeyWorld
Now listening on: https://localhost:5001
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
Para testar nosso novo aplicativo agora que ele está em execução, basta acessar seu navegador assim:
Lidar com a configuração HTTPS está fora do escopo deste artigo
Observe novamente que estou assumindo que todos os comandos estão sendo executados com o diretório atual definido para a pasta raiz da solução. Se o seu diretório atual for um diretório de projeto e houver apenas um arquivo *.csproj nesse diretório, você pode simplesmente digitar "executar dotnet".
Agora que temos um aplicativo de API da web testado, vamos dar o próximo passo, colocar o HeyWorld em uma imagem do Docker. Usando o modelo padrão para a dockerização de um aplicativo .NET Core, adicionaremos um Dockerfile ao nosso projeto HeyWorld com este conteúdo:
FROM microsoft/dotnet:sdk AS build-env
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out
# Build runtime image
FROM microsoft/dotnet:aspnetcore-runtime
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "HeyWorld.dll"]
(Observe que o texto anterior deve ser salvo em um arquivo de texto chamado Dockerfile no diretório do projeto - nesse caso, HeyWorld\Dockerfile.)
Como este artigo é apenas sobre o dotnet cli, eu só quero me concentrar nos dois usos do que dentro do Dockerfile:
- "dotnet restore" - como aprendemos acima, este comando resolverá todas as dependências Nuget do aplicativo
- "dotnet publish -c Release -o out" - o comando "dotnet publish" criará o projeto designado e copiará todos os arquivos que compõem o aplicativo para um determinado local. No nosso caso, o "dotnet publish" copiará o assembly compilado para o HeyWorld, todos os assemblies referenciados a partir das dependências do Nuget, arquivos de configuração e quaisquer arquivos referenciados no arquivo csproj.
Por favor, note no uso acima que nós tivemos que explicitamente dizer "dotnet publish" para compilar com a configuração "Release" através do uso do flag "-c Release". Qualquer comando dotnet cli que compile o código ("build", "publish", "pack", por exemplo) será um assembly de construção padrão com a configuração "Debug". Observe este comportamento e lembre-se de especificar o "-c Release" ou "--configuration Release" se você estiver publicando um Nuget ou um aplicativo destinado ao uso em produção. Você foi avisado.
Apenas para completar o círculo, agora podemos construir e implantar nosso pequeno aplicativo HeyWorld através do Docker com estes comandos:
docker build -t heyworld .
docker run -d -p 8080:80 --name myapp heyworld
Resumo
O dotnet cli torna a automação e a criação de scripts em projetos .NET simples, especialmente em comparação ao estado da arte no .NET há uma década ou mais. Em muitos casos, você pode até mesmo evitar qualquer tipo de ferramenta de script de construção baseada em tarefas (Cake, Fake, Rake, Psake, etc.) em favor de scripts de shell simples que apenas delegam para o dotnet cli. Além disso, o modelo de extensibilidade dotnet cli facilita a incorporação de aplicativos de linha de comando externos criados pelo .NET, distribuídos via Nuget em suas compilações automatizadas.
Sobre o autor
When Jeremy Miller estava crescendo em uma comunidade agrícola no Missouri, havia uma raça "especial" de pessoas ao redor chamada Shade Tree Mechanics. Geralmente eles não eram as pessoas mais respeitáveis do mundo, mas tinham um jeito de consertar problemas mecânicos e um intrépido destemor para consertar qualquer coisa. Um mecânico de árvores de sombra pode ser visto encontrando o par de pernas saindo de debaixo de um carro batedor em quadras, cercado por outros veículos esqueléticos que se amontoam no resto de seu quintal sujo e cheio de lixo. Os batedores que você vê abandonados ao redor dele não são inúteis, eles são forrageiros. Ele pega pedaços, ajusta e ajusta e cria uma solução criativa para as suas necessidades. Reputação não obstante, um mecânico de sombra de árvore sabe como fazer as coisas funcionarem. Embora Miller não tenha nenhuma habilidade mecânica específica (apesar de se formar em engenharia mecânica), ele gosta de pensar que ele é o equivalente desenvolvedor de um mecânico de árvores de sombra. Seu disco rígido está certamente repleto de detritos de projetos de código aberto abandonados.
Com o lançamento do .NET Core 2.0, a Microsoft tem a próxima versão principal da plataforma de propósito geral, modular, multiplataforma e de código aberto que foi lançada inicialmente em 2016. O .NET Core foi criado para ter muitas das APIs que são disponível na versão atual do .NET Framework. Ele foi inicialmente criado para permitir a próxima geração de soluções ASP.NET, mas agora impulsiona e é a base para muitos outros cenários, incluindo IoT, nuvem e soluções móveis de próxima geração. Nesta segunda série, abrangendo o .NET Core, exploraremos mais alguns benefícios do .NET Core e como ele pode beneficiar não apenas desenvolvedores tradicionais de .NET, mas também todos os tecnólogos que precisam levar soluções robustas, eficazes e econômicas ao mercado.
Este artigo da InfoQ faz parte da série ".NET Core". Você pode se inscrever para receber notificações via RSS.