.NET sur Linux : plus simple qu'il n'y paraît

Publié: 2022-08-16

Développer des solutions .NET sur Linux a toujours été un défi car Visual Studio de Microsoft nécessite Windows pour fonctionner. Après avoir travaillé sur plusieurs projets .NET, j'ai décidé de tester les limites de développement de .NET sous Linux. Ce didacticiel simple se concentre sur une application ASP.NET MVC avec SQL Server pour montrer à quel point le développement .NET peut être élégant et efficace sur mon système d'exploitation préféré.

Environnement de développement

Tout d'abord, nous devons nous assurer que les outils .NET et le SDK associés à notre version particulière de Linux sont installés à l'aide du guide standard de Microsoft.

Mon environnement de développement préféré consiste en un environnement de développement intégré (IDE) fenêtré, un puissant outil de gestion et de requête de base de données, la base de données elle-même et des outils de construction et de déploiement. J'utilise les outils suivants pour obtenir des fonctionnalités solides et permettre une belle expérience de codage :

  • EDI : Code Visual Studio
  • Outil de gestion et de requête de base de données : DBeaver
  • Base de données : Microsoft SQL Server (Installation Linux)
  • Outils de génération : interface de ligne de commande (CLI) du SDK .NET
  • Machine virtuelle et conteneurs : Docker

Assurez-vous que ces outils sont correctement installés avant de poursuivre avec notre exemple d'application.

Échafaudage de projet

Dans cet exemple d'application, nous mettrons en évidence le développement et les fonctionnalités d'ASP.NET à travers une série de cas d'utilisation pour un système hypothétique de gestion des stocks de magasin de chaussures. Comme pour toute nouvelle application .NET, nous devrons créer une solution, puis y ajouter un projet. Nous pouvons tirer parti des outils CLI du SDK .NET pour échafauder notre nouvelle solution :

 mkdir Shoestore && cd Shoestore dotnet new sln

Ensuite, créez un projet ASP.NET contenant une classe principale explicite par souci de simplicité, car cette structure de projet est la plus familière aux développeurs ASP.NET. Créons notre projet en utilisant le modèle MVC :

 mkdir Shoestore.mvc && cd Shoestore.mvc dotnet new mvc --use-program-main=true

Ensuite, ajoutez le projet dans la solution :

 # Go to the root of the solution cd .. dotnet sln add Shoestore.mvc/

Nous avons maintenant une solution par défaut et son projet ASP.NET contenu. Avant de continuer, assurez-vous que tout se construit :

 cd Shoestore.mvc/ dotnet restore dotnet build

Les bonnes pratiques de développement encouragent à placer les services clés et l'exécution de l'application dans des conteneurs Docker pour un déploiement et une portabilité améliorés. Par conséquent, créons un conteneur Docker simple pour prendre en charge notre application.

Portabilité des applications

Les images Docker font généralement référence à une autre image Docker parente comme point de départ accepté pour les exigences essentielles telles que le système d'exploitation et les solutions de base, y compris les bases de données. En suivant cette bonne pratique Docker, créez à la fois un fichier Dockerfile et un fichier Docker Compose pour une configuration de service appropriée tout en faisant référence aux images parentes publiées par Microsoft. Nous utiliserons les étapes Docker pour garder notre image petite. Les étapes nous permettent d'utiliser le SDK .NET lors de la création de notre application afin que le runtime ASP.NET ne soit requis que pendant l'exécution de notre application.

Créez le Shoestore.mvc Shoestore.mvc avec le contenu suivant :

 # 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" ]

Ensuite, nous allons créer le fichier docker-compose.yml dans le répertoire racine de notre solution. Initialement, il ne contiendra qu'une référence au .Dockerfile de notre service applicatif :

 # Shoestore/docker-compose.yml version: "3.9" services: web: build: context: . dockerfile: Shoestore.mvc/Dockerfile ports: - "8080:80"

Configurons également notre environnement avec un fichier .dockerignore pour nous assurer que seuls les artefacts de build sont copiés dans notre image.

Avec notre service d'application maintenant intégré et son environnement d'exécution prêt à fonctionner, nous devons créer notre service de base de données et le connecter à notre configuration Docker.

Le service de base de données

L'ajout de Microsoft SQL Server à notre configuration Docker est simple, d'autant plus que nous utilisons une image Docker fournie par Microsoft sans la modifier. Ajoutez le bloc de configuration suivant au bas du fichier docker-compose.yml pour configurer la base de données :

 db: image: "mcr.microsoft.com/mssql/server" environment: SA_PASSWORD: "custom_password_123" ACCEPT_EULA: "Y" ports: - "1433:1433"

Ici, ACCEPT_EULA empêche l'installation de s'arrêter et notre paramètre de ports laisse passer le port SQL Server par défaut sans traduction. Avec cela, notre fichier Compose comprend à la fois notre service d'application et notre base de données.

Avant de personnaliser le code de l'application, vérifions que notre environnement Docker fonctionne :

 # From the root of the solution docker compose up --build

En supposant qu'aucune erreur n'apparaisse au démarrage, notre exemple d'application incomplet devrait être disponible via un navigateur Web à l'adresse locale http://localhost:8080 .

Outils de génération de code

Nous allons maintenant nous concentrer sur la partie amusante : personnaliser le code de l'application et veiller à ce que les données de l'application persistent dans la base de données Microsoft SQL Server. Nous utiliserons à la fois les outils Entity Framework (EF) et .NET SDK pour connecter l'application à la base de données et structurer le modèle, la vue, le contrôleur et la configuration requise par EF de l'application.

Avant de pouvoir spécifier les outils dont nous avons besoin, nous devons créer un fichier tool-manifest :

 # From the root of the solution dotnet new tool-manifest

Ajoutez les outils EF et SDK à ce fichier avec ces commandes simples :

 dotnet tool install dotnet-ef dotnet tool install dotnet-aspnet-codegenerator

Pour vérifier la bonne installation de ces outils, exécutez dotnet ef . Si une licorne apparaît, ils sont installés correctement. Ensuite, exécutez dotnet aspnet-codegenerator pour tester les outils ASP.NET ; la sortie doit être un bloc d'utilisation CLI général.

Nous pouvons maintenant utiliser ces outils pour créer notre application.

MVC : Modèle

La première tâche dans la construction de notre application est la création du modèle. Étant donné que ce modèle sera ajouté ultérieurement à la base de données, nous inclurons les packages MS SQL Server et EF dans notre projet :

 cd Shoestore.mvc/ dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet restore

Ensuite, créez un objet de contexte de base de données EF qui détermine quels modèles sont ajoutés à la base de données et permet à notre code d'accéder facilement à ces données et de les interroger à partir de la base de données.

Créez un répertoire Data pour héberger le code spécifique à EF et créez le fichier Data/ApplicationDBContext.cs avec le contenu suivant :

 // Shoestore/Shoestore.mvc/Data/ApplicationDBContext.cs using Microsoft.EntityFrameworkCore; namespace Shoestore.mvc.Data; public class ApplicationDBContext : DbContext { public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options):base(options){} }

Ensuite, configurez la chaîne de connexion à la base de données, qui doit correspondre aux informations d'identification que nous avons configurées dans notre Dockerfile . Définissez le contenu de Shoestore/Shoestore.mvc/appsettings.json comme suit :

 { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "Shoestore": "Server=db;Database=master;User=sa;Password=custom_password_123;" } }

Avec la chaîne de connexion à la base de données configurée et le contexte de la base de données codé, nous sommes prêts à coder la fonction Main de notre application. Nous inclurons la gestion des exceptions de base de données pour simplifier le débogage du système. De plus, étant donné qu'un bogue .NET dans le code généré fait que le conteneur Docker ne sert pas correctement nos vues, nous devrons ajouter un code spécifique à notre configuration de service de vue. Cela définira explicitement les chemins de fichier vers notre emplacement de vue dans notre image 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"); });

Passez à l' IsDevelopment if dans le même fichier pour ajouter un point de terminaison de migration de base de données à notre système lorsqu'il est en mode développement. Ajoutez une instruction else avec le code suivant :

 // 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(); }

Ensuite, exécutez un test rapide pour vous assurer que les nouveaux packages et les modifications du code source se compilent correctement :

 // Go to mvc directory cd Shoestore.mvc dotnet restore dotnet build

Maintenant, remplissons le modèle avec nos champs obligatoires en créant le 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 génère du SQL basé sur le modèle associé, son fichier de contexte et tout code EF dans notre application. Les résultats SQL sont ensuite traduits et renvoyés à notre code, si nécessaire. Si nous ajoutons notre modèle Shoe à notre contexte de base de données, EF saura comment traduire entre MS SQL Server et notre application. Faisons cela dans le fichier de contexte de la base de données, 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)); } }

Enfin, nous utiliserons un fichier de migration de base de données pour intégrer notre modèle dans la base de données. L'outil EF crée un fichier de migration spécifique à MS SQL Server en fonction du contexte de la base de données et de son modèle associé (c'est-à-dire Shoe ):

 cd Shoestore.mvc/ dotnet ef migrations add InitialCreate

Attendons d'exécuter notre migration jusqu'à ce que nous ayons un contrôleur et une vue en place.

MVC : contrôleur et vue

Nous allons créer notre contrôleur à l'aide de l'outil de génération de code ASP.NET. Cet outil est très puissant mais nécessite des classes d'assistance spécifiques. Utilisez les packages de style Design pour la structure de contrôleur de base et son intégration EF. Ajoutons ces packages :

 cd Shoestore.mvc\ dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design && \ dotnet add package Microsoft.EntityFrameworkCore.Design && \ dotnet restore

Maintenant, créer notre contrôleur par défaut est aussi simple que d'invoquer la commande suivante :

 cd Shoestore.mvc\ dotnet dotnet-aspnet-codegenerator controller \ -name ShoesController \ -m Shoe \ -dc ApplicationDBContext \ --relativeFolderPath Controllers \ --useDefaultLayout \ --referenceScriptLibraries

Lorsque le générateur de code crée notre contrôleur, il crée également une vue simple pour ce contrôleur. Une fois nos fondations MVC terminées, nous sommes prêts à tout faire fonctionner.

Migration et test d'application

Les migrations EF sont généralement une affaire simple, mais lorsque Docker est impliqué, le processus devient plus complexe. Dans le prochain article de notre série, nous explorerons le chemin merveilleusement sinueux pour faire fonctionner ces migrations dans notre solution Docker, mais pour l'instant, nous voulons juste que notre migration s'exécute.

Tous les fichiers de configuration et de migration sont inclus dans notre référentiel. Clonons le projet complet sur notre ordinateur local et effectuons la migration :

 git clone https://github.com/theZetrax/dot-net-on-linux.git cd ./dot-net-on-linux docker composer up

L'opération docker composer construit notre application, exécute la migration et lance notre application ASP.NET avec le runtime .NET. Pour accéder à la solution en cours d'exécution, visitez http://localhost:8080/Shoes .

Bien que notre interface d'application soit simple, elle présente des fonctionnalités à tous les niveaux, de la vue à la base de données.

.NET sur Linux fonctionne tout simplement

Consultez le référentiel complet pour un aperçu de notre solution. Le prochain article couvrira notre migration en détail, ainsi que des trucs et astuces pour alléger nos images Docker.

.NET sur Linux est plus qu'une simple chimère : c'est une combinaison viable de langage, d'exécution et de système d'exploitation. De nombreux développeurs élevés sur Visual Studio n'ont peut-être pas l'expérience de l'utilisation de .NET CLI au maximum, mais ces outils sont efficaces et puissants.

Lectures complémentaires sur le blog Toptal Engineering :

  • Construire une API Web ASP.NET avec ASP.NET Core
  • 8 raisons pour lesquelles Microsoft Stack est toujours un choix viable
  • Comment améliorer les performances des applications ASP.NET dans une batterie de serveurs Web avec mise en cache
  • Un tutoriel Elasticsearch pour les développeurs .NET
  • Qu'est-ce que Kubernetes ? Guide de conteneurisation et de déploiement

Le blog Toptal Engineering exprime sa gratitude à Henok Tsegaye pour avoir examiné les exemples de code présentés dans cet article.