.NET no Linux: mais simples do que parece
Publicados: 2022-08-16O desenvolvimento de soluções .NET no Linux sempre foi um desafio porque o Visual Studio da Microsoft requer o Windows para funcionar. Depois de trabalhar em vários projetos .NET, decidi testar os limites do desenvolvimento do .NET no Linux. Este tutorial simples se concentra em um aplicativo ASP.NET MVC com SQL Server para mostrar como o desenvolvimento .NET pode ser elegante e eficaz no meu sistema operacional preferido.
Ambiente de desenvolvimento
Primeiro, devemos garantir que as ferramentas .NET e o SDK associados ao nosso tipo específico de Linux sejam instalados usando o guia padrão da Microsoft.
Meu ambiente de desenvolvimento preferido consiste em um ambiente de desenvolvimento integrado (IDE) em janela, uma poderosa ferramenta de consulta e gerenciamento de banco de dados, o próprio banco de dados e ferramentas para construção e implantação. Eu uso as seguintes ferramentas para obter uma funcionalidade sólida e permitir uma bela experiência de codificação:
- IDE: código do Visual Studio
- Ferramenta de consulta e gerenciamento de banco de dados: DBeaver
- Banco de dados: Microsoft SQL Server (instalação Linux)
- Ferramentas de compilação: Interface de linha de comando (CLI) do SDK do .NET
- Máquina virtual e contêineres: Docker
Certifique-se de que essas ferramentas estejam instaladas corretamente antes de continuar com nosso aplicativo de exemplo.
Andaimes do Projeto
Neste aplicativo de exemplo, destacaremos o desenvolvimento e a funcionalidade do ASP.NET por meio de uma série de casos de uso para um sistema de gerenciamento de inventário de loja de calçados hipotético. Como acontece com qualquer novo aplicativo .NET, precisaremos criar uma solução e adicionar um projeto a ela. Podemos aproveitar as ferramentas .NET SDK CLI para estruturar nossa nova solução:
mkdir Shoestore && cd Shoestore dotnet new sln
Em seguida, crie um projeto ASP.NET contendo uma classe principal explícita para simplificar, pois essa estrutura de projeto é mais familiar aos desenvolvedores ASP.NET. Vamos criar nosso projeto usando o padrão MVC:
mkdir Shoestore.mvc && cd Shoestore.mvc dotnet new mvc --use-program-main=true
Em seguida, adicione o projeto à solução:
# Go to the root of the solution cd .. dotnet sln add Shoestore.mvc/
Agora temos uma solução padrão e seu projeto ASP.NET contido. Antes de continuar, certifique-se de que tudo seja compilado:
cd Shoestore.mvc/ dotnet restore dotnet build
As boas práticas de desenvolvimento incentivam a colocação de serviços-chave e o tempo de execução do aplicativo em contêineres do Docker para melhor implantação e portabilidade. Portanto, vamos criar um contêiner Docker simples para dar suporte ao nosso aplicativo.
Portabilidade do aplicativo
As imagens do Docker normalmente fazem referência a outra imagem do Docker pai como um ponto de partida aceito para requisitos essenciais, como SO e soluções básicas, incluindo bancos de dados. Seguindo esta prática recomendada do Docker, crie um arquivo Dockerfile e um arquivo Docker Compose para a configuração de serviço adequada ao fazer referência a imagens pai publicadas pela Microsoft. Usaremos estágios do Docker para manter nossa imagem pequena. Os estágios nos permitem usar o SDK do .NET enquanto criamos nosso aplicativo para que o tempo de execução do ASP.NET seja necessário apenas enquanto nosso aplicativo é executado.
Crie o Shoestore.mvc
Shoestore.mvc com o seguinte conteúdo:
# Shoestore\Shoestore.mvc\Dockerfile # Build stage FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /shoestore COPY Shoestore.mvc/*.csproj ./ # Restore project packages RUN dotnet restore COPY Shoestore.mvc/* ./ # Create a release build RUN dotnet build -c Release -o /app/build # Run the application and make it available on port 80 FROM mcr.microsoft.com/dotnet/aspnet:6.0 WORKDIR /app EXPOSE 80 # Assets and views COPY Shoestore.mvc/Views ./Views COPY Shoestore.mvc/wwwroot ./wwwroot COPY --from=build /app/build ./ ENTRYPOINT [ "dotnet", "Shoestore.mvc.dll" ]
Em seguida, criaremos o arquivo docker-compose.yml
no diretório raiz da nossa solução. Inicialmente, ele conterá apenas uma referência ao .Dockerfile
do nosso serviço de aplicativo:
# Shoestore/docker-compose.yml version: "3.9" services: web: build: context: . dockerfile: Shoestore.mvc/Dockerfile ports: - "8080:80"
Vamos também configurar nosso ambiente com um arquivo .dockerignore para garantir que apenas os artefatos de compilação sejam copiados para nossa imagem.
Com nosso serviço de aplicativo agora integrado e seu ambiente de execução pronto para ser executado, precisamos criar nosso serviço de banco de dados e conectá-lo à nossa configuração do Docker.
O serviço de banco de dados
Adicionar o Microsoft SQL Server à nossa configuração do Docker é simples, especialmente porque estamos usando uma imagem do Docker fornecida pela Microsoft sem alterá-la. Adicione o seguinte bloco de configuração à parte inferior do arquivo docker-compose.yml
para configurar o banco de dados:
db: image: "mcr.microsoft.com/mssql/server" environment: SA_PASSWORD: "custom_password_123" ACCEPT_EULA: "Y" ports: - "1433:1433"
Aqui, ACCEPT_EULA
impede que a instalação seja interrompida e nossa configuração de ports
permite que a porta padrão do SQL Server passe sem tradução. Com isso, nosso arquivo Compose inclui nosso serviço de aplicativos e banco de dados.
Antes de personalizar o código do aplicativo, vamos verificar se nosso ambiente Docker funciona:
# From the root of the solution docker compose up --build
Supondo que nenhum erro apareça durante a inicialização, nosso aplicativo de amostra incompleto deve estar disponível por meio de um navegador da Web no endereço local http://localhost:8080
.
Ferramentas de geração de código
Agora vamos nos concentrar na parte divertida: personalizar o código do aplicativo e garantir que os dados do aplicativo persistam no banco de dados do Microsoft SQL Server. Usaremos as ferramentas Entity Framework (EF) e .NET SDK para conectar o aplicativo ao banco de dados e estruturar o modelo do aplicativo, a exibição, o controlador e a configuração exigida pelo EF.
Antes de podermos especificar as ferramentas de que precisamos, devemos criar um arquivo tool-manifest
:
# From the root of the solution dotnet new tool-manifest
Adicione as ferramentas EF e SDK a este arquivo com estes comandos simples:
dotnet tool install dotnet-ef dotnet tool install dotnet-aspnet-codegenerator
Para verificar a instalação adequada dessas ferramentas, execute dotnet ef
. Se um unicórnio aparecer, eles estão instalados corretamente. Em seguida, execute dotnet aspnet-codegenerator
para testar as ferramentas ASP.NET; a saída deve ser um bloco de uso geral da CLI.
Agora podemos usar essas ferramentas para criar nosso aplicativo.
MVC: Modelo
A primeira tarefa na construção de nosso aplicativo é criar o modelo. Como esse modelo será adicionado ao banco de dados posteriormente, incluiremos os pacotes MS SQL Server e EF em nosso projeto:
cd Shoestore.mvc/ dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet restore
Em seguida, crie um objeto de contexto de banco de dados EF que determina quais modelos são adicionados ao banco de dados e permite que nosso código acesse e consulte facilmente esses dados do banco de dados.
Crie um diretório Data
para hospedar o código específico do EF e crie o arquivo Data/ApplicationDBContext.cs
com o seguinte conteúdo:
// Shoestore/Shoestore.mvc/Data/ApplicationDBContext.cs using Microsoft.EntityFrameworkCore; namespace Shoestore.mvc.Data; public class ApplicationDBContext : DbContext { public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options):base(options){} }
Em seguida, configure a string de conexão do banco de dados, que deve corresponder às credenciais que configuramos em nosso Dockerfile
. Defina o conteúdo de Shoestore/Shoestore.mvc/appsettings.json
para o seguinte:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "Shoestore": "Server=db;Database=master;User=sa;Password=custom_password_123;" } }
Com a string de conexão do banco de dados configurada e o contexto do banco de dados codificado, estamos prontos para codificar a função Main
do nosso aplicativo. Incluiremos o tratamento de exceções do banco de dados para simplificar a depuração do sistema. Além disso, como um bug .NET no código gerado faz com que o contêiner do Docker veicule nossas exibições incorretamente, precisaremos adicionar um código específico à nossa configuração de serviço de exibição. Isso definirá explicitamente os caminhos do arquivo para nosso local de exibição em nossa imagem do Docker:
using Microsoft.EntityFrameworkCore; using Shoestore.mvc.Data; namespace Shoestore.mvc; // ... public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Associate our EF database context and configure it with our connection string var connectionString = builder.Configuration.GetConnectionString("Shoestore"); builder.Services.AddDbContext<ApplicationDBContext>( options => options.UseSqlServer(connectionString)); // Middleware to catch unhandled exceptions and display a stack trace builder.Services.AddDatabaseDeveloperPageExceptionFilter(); // Add services to the container. // ASP.NET has a known issue where the final built app doesn't know where the view // files are (in the Docker container). // The fix is to specifically add view locations. builder.Services .AddControllersWithViews() .AddRazorOptions(options => { options.ViewLocationFormats.Add("/{1}/{0}.cshtml"); options.ViewLocationFormats.Add("/Shared/{0}.cshtml"); });
Pule para a instrução if
IsDevelopment
dentro do mesmo arquivo para adicionar um endpoint de migração de banco de dados ao nosso sistema quando ele estiver no modo de desenvolvimento. Adicione uma instrução else
com o seguinte código:
// Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { // Leave the contents of the if block alone. These are hidden for clarity. } else { app.UseMigrationsEndPoint(); }
Em seguida, execute um teste rápido para garantir que os novos pacotes e as edições do código-fonte sejam compilados corretamente:
// Go to mvc directory cd Shoestore.mvc dotnet restore dotnet build
Agora, vamos preencher o modelo com nossos campos obrigatórios criando o Shoestore.mvc\Models\Shoe.cs
:
namespace Shoestore.mvc.Models; public class Shoe { public int ID { get; set; } public string? Name { get; set; } public int? Price { get; set; } public DateTime CreatedDate { get; set; } }
O EF gera SQL com base no modelo associado, seu arquivo de contexto e qualquer código EF em nosso aplicativo. Os resultados do SQL são então traduzidos e retornados ao nosso código, conforme necessário. Se adicionarmos nosso modelo Shoe
ao nosso contexto de banco de dados, a EF saberá como traduzir entre o MS SQL Server e nosso aplicativo. Vamos fazer isso no arquivo de contexto do banco de dados, Shoestore/Shoestore.mvc/Data/ApplicationDBContext.cs
:
using Microsoft.EntityFrameworkCore; using Shoestore.mvc.Models; namespace Shoestore.mvc.Data; public class ApplicationDBContext : DbContext { public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options) : base(options) { } private DbSet<Shoe>? _shoe { get; set; } public DbSet<Shoe> Shoe { set => _shoe = value; get => _shoe ?? throw new InvalidOperationException("Uninitialized property" + nameof(Shoe)); } }
Por fim, usaremos um arquivo de migração de banco de dados para colocar nosso modelo no banco de dados. A ferramenta EF cria um arquivo de migração específico para MS SQL Server com base no contexto do banco de dados e seu modelo associado (ou seja, Shoe
):
cd Shoestore.mvc/ dotnet ef migrations add InitialCreate
Vamos adiar a execução de nossa migração até que tenhamos um controlador e uma visualização no lugar.
MVC: Controlador e Visualização
Criaremos nosso controlador usando a ferramenta de geração de código ASP.NET. Esta ferramenta é muito poderosa, mas requer classes auxiliares específicas. Use os pacotes de estilo Design
para a estrutura básica do controlador e sua integração EF. Vamos adicionar estes pacotes:
cd Shoestore.mvc\ dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design && \ dotnet add package Microsoft.EntityFrameworkCore.Design && \ dotnet restore
Agora, criar nosso controlador padrão é tão simples quanto invocar o seguinte comando:
cd Shoestore.mvc\ dotnet dotnet-aspnet-codegenerator controller \ -name ShoesController \ -m Shoe \ -dc ApplicationDBContext \ --relativeFolderPath Controllers \ --useDefaultLayout \ --referenceScriptLibraries
Quando o gerador de código cria nosso controlador, ele também cria uma visualização simples para esse controlador. Com nossas bases MVC completas, estamos prontos para fazer tudo funcionar.
Migração e teste de aplicativos
As migrações do EF geralmente são simples, mas quando o Docker está envolvido, o processo se torna mais complexo. No próximo artigo de nossa série, exploraremos o caminho maravilhosamente sinuoso para fazer essas migrações funcionarem em nossa solução Docker, mas, por enquanto, queremos apenas que nossa migração seja executada.
Todos os arquivos de configuração e migração estão incluídos em nosso repositório. Vamos clonar o projeto completo para nossa máquina local e realizar a migração:
git clone https://github.com/theZetrax/dot-net-on-linux.git cd ./dot-net-on-linux docker composer up
A operação do docker composer
cria nosso aplicativo, executa a migração e inicia nosso aplicativo ASP.NET com o tempo de execução .NET. Para acessar a solução em execução, visite http://localhost:8080/Shoes
.
Embora nossa interface de aplicativo seja simples, ela demonstra funcionalidade em todas as camadas, desde a visualização até o banco de dados.
.NET no Linux simplesmente funciona
Consulte o repositório completo para obter uma visão geral da nossa solução. O próximo artigo abordará nossa migração em detalhes, bem como dicas e truques para tornar nossas imagens do Docker enxutas.
.NET no Linux é mais do que apenas um sonho: é uma combinação viável de linguagem, tempo de execução e sistema operacional. Muitos desenvolvedores criados no Visual Studio podem não ter experiência no uso do .NET CLI ao máximo, mas essas ferramentas são eficazes e poderosas.
Leitura adicional no Blog da Toptal Engineering:
- Criando uma API Web ASP.NET com ASP.NET Core
- 8 razões pelas quais o Microsoft Stack ainda é uma escolha viável
- Como melhorar o desempenho do aplicativo ASP.NET no Web Farm com cache
- Um tutorial do Elasticsearch para desenvolvedores .NET
- O que é Kubernetes? Um guia para conteinerização e implantação
O Toptal Engineering Blog agradece a Henok Tsegaye por revisar os exemplos de código apresentados neste artigo.