.NET su Linux: più semplice di quanto sembri
Pubblicato: 2022-08-16Lo sviluppo di soluzioni .NET su Linux è sempre stato impegnativo perché Visual Studio di Microsoft richiede Windows per funzionare. Dopo aver lavorato su diversi progetti .NET, ho deciso di testare i limiti dello sviluppo di .NET su Linux. Questo semplice tutorial si concentra su un'applicazione ASP.NET MVC con SQL Server per mostrare quanto può essere elegante ed efficace lo sviluppo .NET sul mio sistema operativo preferito.
Sviluppo dell'ambiente
Innanzitutto, dobbiamo assicurarci che gli strumenti .NET e l'SDK associati al nostro particolare tipo di Linux siano installati utilizzando la guida standard di Microsoft.
Il mio ambiente di sviluppo preferito consiste in un ambiente di sviluppo integrato (IDE) finestrato, un potente strumento di gestione e query del database, il database stesso e strumenti per la creazione e la distribuzione. Uso i seguenti strumenti per ottenere funzionalità solide e consentire una bellissima esperienza di codifica:
- IDE: codice di Visual Studio
- Strumento di gestione e query del database: DBeaver
- Database: Microsoft SQL Server (installazione Linux)
- Strumenti di compilazione: .NET SDK Command Line Interface (CLI)
- Macchina virtuale e contenitori: Docker
Assicurati che questi strumenti siano installati correttamente prima di procedere con la nostra applicazione di esempio.
Ponteggi di progetto
In questa applicazione di esempio, evidenzieremo lo sviluppo e la funzionalità di ASP.NET attraverso una serie di casi d'uso per un ipotetico sistema di gestione dell'inventario di un negozio di scarpe. Come con qualsiasi nuova applicazione .NET, dovremo creare una soluzione e quindi aggiungervi un progetto. Possiamo sfruttare gli strumenti della CLI di .NET SDK per supportare la nostra nuova soluzione:
mkdir Shoestore && cd Shoestore dotnet new sln
Quindi, per semplicità, crea un progetto ASP.NET contenente una classe principale esplicita, poiché questa struttura del progetto è più familiare agli sviluppatori ASP.NET. Creiamo il nostro progetto utilizzando il pattern MVC:
mkdir Shoestore.mvc && cd Shoestore.mvc dotnet new mvc --use-program-main=true
Quindi, aggiungi il progetto nella soluzione:
# Go to the root of the solution cd .. dotnet sln add Shoestore.mvc/
Ora abbiamo una soluzione predefinita e il suo progetto ASP.NET contenuto. Prima di procedere, assicurati che tutto costruisca:
cd Shoestore.mvc/ dotnet restore dotnet build
Le buone pratiche di sviluppo incoraggiano a inserire i servizi chiave e il runtime dell'applicazione nei contenitori Docker per una migliore distribuzione e portabilità. Pertanto, creiamo un semplice contenitore Docker per supportare la nostra applicazione.
Portabilità dell'applicazione
Le immagini Docker in genere fanno riferimento a un'altra immagine Docker padre come punto di partenza accettato per requisiti essenziali come il sistema operativo e le soluzioni di base, inclusi i database. Seguendo questa procedura consigliata per Docker, crea sia un file Docker che un file Docker Compose per una corretta configurazione del servizio facendo riferimento alle immagini principali pubblicate da Microsoft. Useremo le fasi Docker per mantenere piccola la nostra immagine. Le fasi ci consentono di utilizzare .NET SDK durante la creazione della nostra applicazione in modo che il runtime ASP.NET sia richiesto solo durante l'esecuzione della nostra applicazione.
Crea il Dockerfile Shoestore.mvc
con i seguenti contenuti:
# 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" ]
Successivamente, creeremo il file docker-compose.yml
nella directory principale della nostra soluzione. Inizialmente, conterrà solo un riferimento al .Dockerfile
del nostro servizio applicativo:
# Shoestore/docker-compose.yml version: "3.9" services: web: build: context: . dockerfile: Shoestore.mvc/Dockerfile ports: - "8080:80"
Configuriamo anche il nostro ambiente con un file .dockerignore per garantire che solo gli artefatti di compilazione vengano copiati nella nostra immagine.
Con il nostro servizio applicativo ora inserito e il suo ambiente di esecuzione pronto per l'esecuzione, dobbiamo creare il nostro servizio di database e collegarlo alla nostra configurazione Docker.
Il servizio di database
L'aggiunta di Microsoft SQL Server alla nostra configurazione Docker è semplice, soprattutto perché stiamo utilizzando un'immagine Docker fornita da Microsoft senza modificarla. Aggiungi il seguente blocco di configurazione in fondo al file docker-compose.yml
per configurare il database:
db: image: "mcr.microsoft.com/mssql/server" environment: SA_PASSWORD: "custom_password_123" ACCEPT_EULA: "Y" ports: - "1433:1433"
Qui, ACCEPT_EULA
impedisce l'interruzione dell'installazione e la nostra impostazione delle ports
consente il passaggio della porta predefinita di SQL Server senza traduzione. Con ciò, il nostro file Compose include sia il nostro servizio applicativo che il database.
Prima di personalizzare il codice dell'applicazione, verifichiamo che il nostro ambiente Docker funzioni:
# From the root of the solution docker compose up --build
Supponendo che non vengano visualizzati errori durante l'avvio, la nostra applicazione di esempio incompleta dovrebbe essere disponibile tramite un browser Web all'indirizzo locale http://localhost:8080
.
Strumenti di generazione del codice
Ora ci concentriamo sulla parte divertente: personalizzare il codice dell'applicazione e garantire che i dati dell'applicazione persistano nel database di Microsoft SQL Server. Utilizzeremo gli strumenti Entity Framework (EF) e .NET SDK per connettere l'applicazione al database e impalcare il modello dell'applicazione, la visualizzazione, il controller e la configurazione richiesta da EF.
Prima di poter specificare gli strumenti di cui abbiamo bisogno, dobbiamo creare un file tool-manifest
:
# From the root of the solution dotnet new tool-manifest
Aggiungi gli strumenti EF e SDK a questo file con questi semplici comandi:
dotnet tool install dotnet-ef dotnet tool install dotnet-aspnet-codegenerator
Per verificare la corretta installazione di questi strumenti, eseguire dotnet ef
. Se appare un unicorno, sono installati correttamente. Quindi, esegui dotnet aspnet-codegenerator
per testare gli strumenti ASP.NET; l'output dovrebbe essere un blocco di utilizzo CLI generale.
Ora possiamo usare questi strumenti per creare la nostra applicazione.
MVC: modello
Il primo compito nella creazione della nostra applicazione è la creazione del modello. Poiché questo modello verrà aggiunto al database in un secondo momento, includeremo i pacchetti MS SQL Server ed EF nel nostro progetto:
cd Shoestore.mvc/ dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet restore
Quindi, crea un oggetto contesto del database EF che determini quali modelli vengono aggiunti al database e consenta al nostro codice di accedere facilmente e interrogare quei dati dal database.
Creare una directory Data
per ospitare il codice specifico di EF e creare il file Data/ApplicationDBContext.cs
con i seguenti contenuti:
// Shoestore/Shoestore.mvc/Data/ApplicationDBContext.cs using Microsoft.EntityFrameworkCore; namespace Shoestore.mvc.Data; public class ApplicationDBContext : DbContext { public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options):base(options){} }
Quindi, configura la stringa di connessione al database, che deve corrispondere alle credenziali che abbiamo configurato nel nostro Dockerfile
. Imposta il contenuto di Shoestore/Shoestore.mvc/appsettings.json
come segue:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "Shoestore": "Server=db;Database=master;User=sa;Password=custom_password_123;" } }
Con la stringa di connessione al database configurata e il contesto del database codificato, siamo pronti per codificare la funzione Main
della nostra applicazione. Includeremo la gestione delle eccezioni del database per semplificare il debug del sistema. Inoltre, poiché un bug .NET nel codice generato fa sì che il contenitore Docker serva le nostre viste in modo errato, dovremo aggiungere codice specifico alla nostra configurazione del servizio di visualizzazione. Questo imposterà esplicitamente i percorsi dei file nella nostra posizione di visualizzazione nella nostra immagine 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"); });
Passa IsDevelopment
if
all'interno dello stesso file per aggiungere un endpoint di migrazione del database al nostro sistema quando è in modalità di sviluppo. Aggiungi else
istruzione con il codice seguente:
// 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(); }
Quindi, esegui un rapido test per assicurarti che i nuovi pacchetti e le modifiche al codice sorgente vengano compilati correttamente:
// Go to mvc directory cd Shoestore.mvc dotnet restore dotnet build
Ora, popola il modello con i nostri campi obbligatori creando il file 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; } }
EF genera SQL in base al modello associato, al relativo file di contesto e a qualsiasi codice EF nella nostra applicazione. I risultati SQL vengono quindi tradotti e restituiti al nostro codice, se necessario. Se aggiungiamo il nostro modello di Shoe
al nostro contesto di database, EF saprà come tradurre tra MS SQL Server e la nostra applicazione. Facciamolo nel file di contesto del database, 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)); } }
Infine, utilizzeremo un file di migrazione del database per inserire il nostro modello nel database. Lo strumento EF crea un file di migrazione specifico per MS SQL Server in base al contesto del database e al modello associato (ad esempio, Shoe
):
cd Shoestore.mvc/ dotnet ef migrations add InitialCreate
Asteniamoci dall'esecuzione della nostra migrazione fino a quando non avremo un controller e una visualizzazione in atto.
MVC: controller e vista
Creeremo il nostro controller utilizzando lo strumento di generazione del codice ASP.NET. Questo strumento è molto potente ma richiede classi di supporto specifiche. Utilizzare i pacchetti di stile Design
per la struttura del controller di base e la sua integrazione EF. Aggiungiamo questi pacchetti:
cd Shoestore.mvc\ dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design && \ dotnet add package Microsoft.EntityFrameworkCore.Design && \ dotnet restore
Ora, creare il nostro controller predefinito è semplice come invocare il seguente comando:
cd Shoestore.mvc\ dotnet dotnet-aspnet-codegenerator controller \ -name ShoesController \ -m Shoe \ -dc ApplicationDBContext \ --relativeFolderPath Controllers \ --useDefaultLayout \ --referenceScriptLibraries
Quando il generatore di codice crea il nostro controller, crea anche una vista semplice per quel controller. Con le nostre basi MVC complete, siamo pronti per far funzionare tutto.
Migrazione e test applicativo
Le migrazioni di EF sono in genere un affare semplice, ma quando è coinvolto Docker, il processo diventa più complesso. Nel prossimo articolo della nostra serie, esploreremo il percorso meravigliosamente tortuoso per far funzionare queste migrazioni nella nostra soluzione Docker, ma per ora vogliamo solo che la nostra migrazione venga eseguita.
Tutti i file di configurazione e migrazione sono inclusi nel nostro repository. Cloniamo il progetto completo sulla nostra macchina locale ed eseguiamo la migrazione:
git clone https://github.com/theZetrax/dot-net-on-linux.git cd ./dot-net-on-linux docker composer up
L'operazione di docker composer
crea la nostra applicazione, esegue la migrazione e avvia la nostra applicazione ASP.NET con il runtime .NET. Per accedere alla soluzione in esecuzione, visitare http://localhost:8080/Shoes
.
Sebbene la nostra interfaccia dell'applicazione sia semplice, dimostra la funzionalità attraverso tutti i livelli, dalla visualizzazione fino al database.
.NET su Linux funziona e basta
Consulta il repository completo per una panoramica della nostra soluzione. Il prossimo articolo tratterà in dettaglio la nostra migrazione, oltre a suggerimenti e trucchi per rendere le nostre immagini Docker snelle.
.NET su Linux è più di un semplice sogno irrealizzabile: è una valida combinazione di linguaggio, runtime e sistema operativo. Molti sviluppatori cresciuti su Visual Studio potrebbero non avere esperienza nell'utilizzo completo di .NET CLI, ma questi strumenti sono efficaci e potenti.
Ulteriori letture sul blog di Toptal Engineering:
- Creazione di un'API Web ASP.NET con ASP.NET Core
- 8 motivi per cui Microsoft Stack è ancora una scelta praticabile
- Come migliorare le prestazioni dell'app ASP.NET nella Web Farm con la memorizzazione nella cache
- Un tutorial su Elasticsearch per sviluppatori .NET
- Cos'è Kubernetes? Una guida alla containerizzazione e alla distribuzione
Il Toptal Engineering Blog estende la sua gratitudine a Henok Tsegaye per aver esaminato gli esempi di codice presentati in questo articolo.