Objectifs pédagogiques
Ce chapitre a pour vocation de fournir les clés pratiques et architecturales de la plateforme Microsoft .NET (Network Enabled Technologies). À l'issue de ce cours, l'étudiant sera capable de :
1. Comprendre l'évolution .NET
Distinguer le .NET Framework historique du .NET moderne (.NET Core / .NET 8 et 9), multiplateforme, open-source et optimisé pour les très hautes performances.
2. Maîtriser le typage C# (C-Sharp)
Manipuler les types valeurs (sur la pile Stack), les types références (sur le tas Heap) et les collections génériques indispensables (List<T>, Dictionary).
3. Modéliser des relations en POO
Structurer des classes C# en POO (Programmation Orientée Objet) avec encapsulation et relations métiers réelles de 1 à N (Un à Plusieurs).
4. Exploiter la mécanique MVC
Comprendre le routage d'URL (Uniform Resource Locator — Adresse web), la coordination par le Contrôleur et la génération de pages HTML5 (HyperText Markup Language 5) via le moteur Razor.
5. Démystifier l'ORM EF Core
Comprendre le rôle d'un ORM (Object-Relational Mapping — Mappage Objet-Relationnel) avec EF Core (Entity Framework Core) par rapport au code SQL (Structured Query Language) natif.
6. Réaliser une application CRUD
Mettre en œuvre un CRUD (Create, Read, Update, Delete — Créer, Lire, Mettre à jour, Supprimer) complet dans un TP (Travaux Pratiques) guidé avec une liste statique en mémoire vive RAM (Random Access Memory).
2.1 Introduction Globale au Framework
La plateforme .NET (Network Enabled Technologies) développée par Microsoft a connu une transformation majeure, passant d'un environnement propriétaire strictement réservé à Windows à un standard technologique mondial, moderne et ouvert.
De .NET Framework à .NET Moderne
Lancé en 2002, .NET Framework était exclusivement lié au système d'exploitation Windows et à son serveur web IIS (Internet Information Services).
À partir de 2016, Microsoft a entièrement repensé l'architecture sous le nom .NET Core, aujourd'hui unifié sous l'appellation .NET moderne (.NET 8 et .NET 9) :
- Multiplateforme native : S'exécute de manière identique sous Linux, macOS, conteneurs Docker et Windows.
- 100% Open Source : Code source public et libre, maintenu par Microsoft et la communauté mondiale (.NET Foundation).
- Modulaire & Léger : Découpé en bibliothèques indépendantes distribuées sous forme de packages légers via le gestionnaire NuGet.
Atouts Clés de l'Écosystème
Les motivations techniques qui font de .NET un choix de premier ordre dans les projets d'ingénierie logicielle :
- Unification complète : Une base technique commune appelée BCL (Base Class Library — Bibliothèque de classes de base) partagée pour le Web, le mobile, le cloud et le poste de travail.
- Sécurité intégrée : Protections automatiques contre les attaques CSRF (Cross-Site Request Forgery — Falsification de requête intersites), XSS (Cross-Site Scripting — Injection de scripts intersites) et les injections SQL (Structured Query Language).
- Injection de Dépendances native : Gestion intégrée de l'IoC (Inversion of Control — Inversion de contrôle) dès le démarrage de l'application sans outil supplémentaire.
- Performances de premier plan : Classé aux premiers rangs mondiaux sur les tests indépendants TechEmpower grâce au serveur web asynchrone ultra-rapide Kestrel.
Évolution comparative des générations .NET
| Critère d'évaluation | .NET Framework (Génération Historique) | .NET Moderne (.NET 8 / .NET 9) |
|---|---|---|
| Systèmes d'exploitation (OS — Operating System) | Windows uniquement | Windows, Linux, macOS, conteneurs Docker, processeurs ARM64 (Advanced RISC Machine 64 bits) |
| Modèle de licence | Propriétaire Microsoft | Open Source libre (Licence MIT — Massachusetts Institute of Technology / Apache 2.0) |
| Serveur Web HTTP (HyperText Transfer Protocol) | IIS (Internet Information Services) dépendant de Windows | Kestrel ultra-léger, autonome et multiplateforme |
| Organisation du système | Monolithique globale via le GAC (Global Assembly Cache — Cache d'assembly global) | Modulaire via des paquets NuGet indépendants |
2.2 Le Langage C# : Performances et Domaines
Le langage C# (prononcé C-Sharp) est un langage moderne, orienté objet et fortement typé. Il allie une grande clarté d'écriture pour le développeur et une excellente rapidité d'exécution sur la machine.
1. Caractéristiques Principales
- POO (Programmation Orientée Objet) : Le code est organisé en classes, objets et méthodes pour modéliser clairement les entités métiers réelles.
- Fortement et statiquement typé : Chaque variable possède un type précis (entier, texte, date...) contrôlé dès la compilation, ce qui élimine la majorité des bugs avant l'exécution.
- Compilation en deux étapes :
- Le code source C# est d'abord traduit en un langage intermédiaire nommé IL (Intermediate Language — Langage Intermédiaire), également appelé CIL (Common Intermediate Language — Langage Intermédiaire Commun).
- Au moment de l'exécution, le compilateur JIT (Just-In-Time — Compilateur Juste-À-Temps / à la volée) de la machine virtuelle CLR (Common Language Runtime — Environnement d'exécution commun) convertit cet IL en instructions binaires compréhensibles par le processeur (CPU — Central Processing Unit).
- Ramasse-miettes (GC — Garbage Collector) : La mémoire est nettoyée automatiquement par le système, évitant au développeur la corvée et les risques d'une gestion manuelle.
2. Performances Simples & Efficaces
Pourquoi le C# est-il reconnu pour sa rapidité ?
- Proche des performances du C++ : Grâce aux optimisations intelligentes du moteur d'exécution et de la mémoire, C# atteint des vitesses de traitement exceptionnelles tout en restant beaucoup plus simple et sécurisé à programmer.
- Exécution native par le processeur : Grâce à la compilation JIT (Just-In-Time), le code s'exécute directement sur le matériel sans couche d'interprétation lente.
- Gestion mémoire optimisée : L'allocation rapide des données permet d'exécuter des dizaines de milliers de requêtes web par seconde avec une consommation minimale de ressources.
3. Domaines d'Utilisation Majeurs
C# s'adapte à de nombreux secteurs professionnels :
- Applications Web Cloud-native : Création de sites web dynamiques avec ASP.NET Core MVC et d'interfaces de programmation Web API (Application Programming Interface) rapides, ainsi que d'applications interactives avec Blazor.
- Développement de Jeux Vidéo : Langage de référence du moteur mondial Unity (jeux 2D, 3D et VR — Virtual Reality / Réalité Virtuelle).
- Applications Desktop (Bureau) lourdes : Logiciels professionnels de gestion via WPF (Windows Presentation Foundation) et applications multiplateformes avec .NET MAUI (Multi-platform App UI — Interface utilisateur d'application multiplateforme).
- Systèmes embarqués : Objets connectés et projets IoT (Internet of Things — Internet des Objets).
Schéma simple : Chaîne de compilation et d'exécution du C#
2.3 Les Types de Données en C#
Le système de types de C# (C-Sharp) repose sur une distinction fondamentale dans la mémoire vive de l'ordinateur (RAM — Random Access Memory) : les types valeurs et les types références.
1. Types Valeurs (Primitives)
Ils stockent directement leur valeur dans un espace mémoire ultra-rapide appelé la pile (Stack) :
int: Entier signé de 32 bits (ex:25).double: Nombre décimal à virgule flottante (64 bits), idéal pour les calculs scientifiques généraux.decimal: Nombre décimal de 128 bits d'une très haute précision, obligatoire pour la finance, les prix et la comptabilité (il élimine les erreurs d'arrondi binaire).bool: Booléen logique valanttrue(vrai) oufalse(faux).
2. Types Références
La variable stocke une adresse (référence) pointant vers les données réelles stockées sur le tas managé (Heap) :
string: Chaîne de caractères de texte. En C#, une chaîne est immuable (non modifiable directement en place : toute modification crée une nouvelle chaîne).class: Modèles et objets créés par le programmeur (ex:Voiture,Etudiant).object: Le type racine universel duquel tous les types C# dérivent.- Ces types peuvent contenir la valeur vide
null.
Les Collections Modernes en C#
Pour organiser et manipuler plusieurs éléments, C# propose des collections génériques performantes présentées en détail ci-dessous :
1. Tableau classique (Array) : T[]
Taille Fixe
Tableau contigu alloué avec une taille fixe dès le départ (ex: int[]).
Cas d'usage : Quand le nombre d'éléments est strictement constant et connu d'avance (ex: les 7 jours de la semaine).
2. Liste Générique : List<T>
Dynamique
Tableau dynamique qui s'agrandit ou rétrécit automatiquement avec les méthodes .Add(), .Remove() et la propriété .Count.
Cas d'usage : Le choix privilégié dans les applications web de gestion pour manipuler des catalogues de produits ou des listes d'utilisateurs.
3. Dictionnaire : Dictionary<TKey, TValue>
Recherche O(1)
Table associative associant une clé unique à une valeur, garantissant un accès instantané dit O(1) (Complexité algorithmique en temps constant).
Cas d'usage : Les caches de données et les recherches directes par code unique ou identifiant.
// 1. Types Valeurs (stockés sur la pile / Stack)
int age = 22;
double vitesse = 90.5;
decimal tarifJour = 110.50m; // Le suffixe 'm' indique explicitement le type decimal (monétaire)
bool estDisponible = true;
// 2. Types Références (stockés sur le tas / Heap)
string nomClient = "Mohamed Trabelsi";
// 3. Collections
// a) Tableau de taille fixe
string[] jours = { "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi" };
// b) Liste dynamique (évolutive)
List<string> cours = new List<string>();
cours.Add("Architecture Web");
cours.Add("Programmation C#");
cours.Add("Bases de données SQL (Structured Query Language)");
cours.Remove("Architecture Web"); // Suppression simple
// c) Dictionnaire Clé-Valeur (recherche instantanée par clé)
Dictionary<string, string> villes = new Dictionary<string, string>();
villes.Add("3000", "Sfax");
villes.Add("1000", "Tunis");
villes.Add("4000", "Sousse");
string maVille = villes["3000"]; // Retourne "Sfax" immédiatement sans parcourir toute la liste
2.4 Programmation Orientée Objet avec C# (Exemple)
La POO (Programmation Orientée Objet) permet de modéliser fidèlement des concepts réels à l'aide de classes, de propriétés auto-implémentées ({ get; set; }) et de relations d'association.
using System;
using System.Collections.Generic;
namespace CoursEnetcom.Models
{
// Modèle représentant un département académique (Côté 1 de la relation)
public class Departement
{
public int Id { get; set; }
public string Nom { get; set; } = string.Empty;
// Relation 1 à N : Un département contient une liste de plusieurs étudiants
public List<Etudiant> Etudiants { get; set; } = new List<Etudiant>();
}
// Modèle représentant un étudiant (Côté N de la relation)
public class Etudiant
{
public int Matricule { get; set; }
public string NomComplet { get; set; } = string.Empty;
// Relation inverse : Clé étrangère et lien vers le département associé
public int DepartementId { get; set; }
public Departement? DepartementAssocie { get; set; }
public void AfficherDetails()
{
// Utilisation de l'opérateur de navigation sécurisée (?.) et de l'interpolation ($"...")
Console.WriteLine($"Étudiant: {NomComplet} - Départ: {DepartementAssocie?.Nom}");
}
}
}
Exemple d'instanciation et d'exécution dans le code
// 1. Instanciation du Département (Côté 1)
var departement = new Departement { Id = 1, Nom = "Informatique" };
// 2. Instanciation de l'Étudiant (Côté N)
var etudiant = new Etudiant
{
Matricule = 101,
NomComplet = "Amine Sassi",
DepartementId = departement.Id,
DepartementAssocie = departement
};
// 3. Ajout de l'étudiant dans la liste du département
departement.Etudiants.Add(etudiant);
// 4. Appel de la méthode
etudiant.AfficherDetails();
// Affiche dans la console : Étudiant: Amine Sassi - Départ: Informatique
2.5 ASP.NET MVC : Composants et Logique du Framework
Le patron d'architecture ASP.NET (Active Server Pages .NET) MVC (Modèle-Vue-Contrôleur / Model-View-Controller) sépare clairement une application web en trois briques complémentaires pour faciliter son organisation et sa maintenance.
Interception et résolution de l'URL
Le mécanisme qui intercepte l'URL (Uniform Resource Locator — Adresse web) saisie dans le navigateur et décide quel Contrôleur et quelle Action exécuter.
Modèle standard : /{controller}/{action}/{id?}
Exemple : L'adresse /Etudiant/Details/5 appelle automatiquement la méthode Details(5) du contrôleur EtudiantController.
Données & Règles Métier
Classes C# représentant les structures de données (tables de bases de données, objets de transfert DTO — Data Transfer Object ou ViewModels spécialisés pour les vues).
Orchestration & Logique de Traitement
Classes C# dérivant de la classe de base Controller. Elles contiennent des méthodes (Actions) qui répondent aux requêtes HTTP (HyperText Transfer Protocol) (verbes GET pour afficher, POST pour envoyer un formulaire) et retournent une vue View().
Affichage & Interface Graphique Utilisateur
Fichiers avec l'extension .cshtml combinant du HTML (HyperText Markup Language) pur et du code C# précédé du symbole @.
Le serveur compile cette vue en HTML standard avant de l'envoyer au navigateur web.
Schéma du cycle de vie d'une requête HTTP dans ASP.NET MVC
2.6 Entity Framework Core (EF Core)
Dans les projets web en production, la gestion manuelle du SQL (Structured Query Language) peut s'avérer répétitive et sujette à erreurs. C'est ici qu'intervient la technologie ORM (Object-Relational Mapping — Mappage Objet-Relationnel) avec EF Core (Entity Framework Core).
Qu'est-ce qu'un ORM ?
Un ORM (Object-Relational Mapping — Mappage Objet-Relationnel) est une couche logicielle qui fait le pont entre le monde orienté objet de votre code C# (classes, propriétés, listes en mémoire) et le monde relationnel de votre SGBD (Système de Gestion de Base de Données) (tables, colonnes et clés SQL).
Le Rôle d'EF Core
- Gain de productivité : On écrit du C# au lieu de longues requêtes SQL manuelles.
- Requêtes typées avec LINQ : Utilisation de LINQ (Language Integrated Query — Requêtes intégrées au langage) permettant à l'éditeur de détecter les erreurs de syntaxe dès la frappe.
- Sécurité anti-injection SQL : Les paramètres sont automatiquement nettoyés et sécurisés par l'ORM.
- Indépendance de la base de données : Fonctionne avec SQL Server, PostgreSQL, MySQL ou SQLite sans changer le code applicatif.
Exemple simple : Comparaison entre SQL Natif et EF Core (Plein écran)
Besoin métier : « Récupérer la liste des voitures disponibles dont le prix par jour est inférieur à 100 DT (Dinars Tunisiens) ».
Approche 1 : SQL Natif classique via ADO.NET (ActiveX Data Objects .NET)
// Code verbeux, manipulation manuelle du curseur et risque d'erreur de frappe
var voitures = new List<Voiture>();
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
string sql = "SELECT Id, Marque, Modele, PrixJour FROM Voitures " +
"WHERE EstDisponible = 1 AND PrixJour < @maxPrix";
using (var command = new SqlCommand(sql, connection))
{
command.Parameters.AddWithValue("@maxPrix", 100m);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
voitures.Add(new Voiture
{
Id = reader.GetInt32(0),
Marque = reader.GetString(1),
Modele = reader.GetString(2),
PrixJour = reader.GetDecimal(3),
EstDisponible = true
});
}
}
}
}
Approche 2 : Entity Framework Core (EF Core) avec LINQ (Language Integrated Query)
// Code concis, élégant, 100% vérifié par le compilateur C#
using (var db = new ApplicationDbContext())
{
var voitures = db.Voitures
.Where(v => v.EstDisponible && v.PrixJour < 100m)
.OrderBy(v => v.Marque)
.ToList();
}
// Avantages :
// 1. Zéro requête SQL écrite sous forme de texte brut
// 2. Si une propriété change de nom, le compilateur signale l'erreur immédiatement
// 3. EF Core génère automatiquement la requête SQL sécurisée et optimisée
| Critère | SQL Natif (ADO.NET — ActiveX Data Objects .NET) | Entity Framework Core (ORM — Mappage Objet-Relationnel) |
|---|---|---|
| Vérification à la compilation | Non (la requête SQL est une simple chaîne de caractères) | Oui (le compilateur C# vérifie la validité des propriétés) |
| Vitesse d'écriture | Lente (obligation de mapper chaque colonne à la main) | Immédiate (une seule ligne LINQ suffit) |
| Sécurité contre l'injection SQL | Dépend de la vigilance du développeur | Automatique et native par paramétrage systématique |
2.7 Structure Typique d'un Projet ASP.NET MVC
Lors de la création d'un projet MVC dans Visual Studio ou via la commande en ligne dotnet new mvc, l'organisation standard des fichiers se présente ainsi sous forme d'une arborescence structurée :
📁 MonProjetWeb/ ├── 📁 Controllers/ → Contient les classes gérant la logique des requêtes (ex: HomeController.cs, VoitureController.cs) ├── 📁 Models/ → Contient les classes métiers et de données (ex: Voiture.cs, Departement.cs) ├── 📁 Views/ → Fichiers d'interface utilisateur Razor (.cshtml) organisés par dossiers portant le nom des contrôleurs │ ├── 📁 Home/ → Vues associées au HomeController (Index.cshtml, Privacy.cshtml) │ ├── 📁 Voiture/ → Vues du contrôleur Voiture (Index.cshtml, Create.cshtml, Edit.cshtml, Delete.cshtml) │ └── 📁 Shared/ → Vues partagées, inclut _Layout.cshtml pour le layout principal (Header/Footer commun à tout le site) ├── 📁 wwwroot/ → Le SEUL dossier accessible publiquement de l'extérieur. Contient les assets statiques (CSS, JS, images) │ ├── 📁 css/ → Feuilles de style CSS (Cascading Style Sheets) │ ├── 📁 js/ → Scripts clients JS (JavaScript) │ └── 📁 lib/ → Bibliothèques clientes tierces (Bootstrap) ├── 📄 appsettings.json → Fichier de configuration (chaînes de connexion à la base de données DB — Database, clés API) └── 📄 Program.cs → Le point d'entrée de l'application. On y configure le pipeline HTTP (middlewares), l'injection de dépendances et le routage
Démystification de chaque répertoire et fichier clé
Une présentation détaillée du répertoire généré par Visual Studio pour démystifier le code :
📁 Controllers/
Contient les classes gérant la logique des requêtes HTTP (HyperText Transfer Protocol) comme HomeController.cs ou VoitureController.cs. Chaque contrôleur intercepte les requêtes de l'utilisateur, prépare les données et retourne la vue adéquate.
📁 Models/
Contient les classes métiers et de données (ex: Voiture.cs, Departement.cs). Ces classes C# (C-Sharp) représentent la structure des données manipulées par l'application et portent les règles de validation associées.
📁 Views/ (Interface Utilisateur)
Les fichiers d'interface utilisateur Razor (.cshtml), organisés par dossiers portant le nom des contrôleurs (ex: Views/Voiture/Index.cshtml). Ils combinent du HTML5 (HyperText Markup Language 5) et du C# via le moteur de template Razor.
Inclut 📁 Shared/ : Réservé au layout principal partagé par toutes les pages. Le fichier _Layout.cshtml définit le Header (en-tête commun avec le menu de navigation) et le Footer (pied de page commun). L'instruction @RenderBody() permet d'injecter la vue spécifique demandée.
📁 wwwroot/ (Racine Web Statique)
Le seul dossier accessible publiquement de l'extérieur par le navigateur web. Il contient les assets statiques servis directement sans passer par un contrôleur : feuilles de style CSS (Cascading Style Sheets), scripts clients JS (JavaScript), images et bibliothèques tierces embarquées (comme Bootstrap).
📄 appsettings.json
Fichier de configuration au format JSON (JavaScript Object Notation). On y définit les paramètres globaux de l'application : chaînes de connexion à la base de données (DB — Database) pour le SGBD (Système de Gestion de Base de Données), niveaux de logs et clés secrètes d'API (Application Programming Interface).
📄 Program.cs
Le point d'entrée de l'application. C'est ici que l'on configure le pipeline HTTP (middlewares), l'injection de dépendances et l'IoC (Inversion of Control — Inversion de contrôle) via builder.Services, et que l'on initialise le serveur web Kestrel et le routage MVC.
2.8 TP (Travaux Pratiques) Guidé : Application MVC de Gestion de Voitures
Objectif du TP (Travaux Pratiques) : Créer une application web complète en ASP.NET Core MVC (Modèle-Vue-Contrôleur) permettant de :
- Afficher la liste des voitures (opération de lecture).
- Ajouter, modifier et supprimer des voitures (opérations d'écriture).
- Conserver les données dans une liste statique en mémoire vive (RAM — Random Access Memory), sans aucune base de données.
private static List<Voiture> dans le contrôleur, cette liste reste vivante en mémoire vive pendant toute la durée d'exécution du serveur web. C'est l'exercice parfait pour maîtriser le mécanisme MVC pur avant d'aborder les bases de données SQL.
Création du Projet ASP.NET Core MVC
Ouvrez votre console ou terminal de commande (CLI — Command Line Interface) et exécutez les deux commandes suivantes :
dotnet new mvc -n GestionVoitures cd GestionVoitures
(Remarque : Si vous utilisez Visual Studio, faites simplement Fichier > Nouveau Projet > Application Web ASP.NET Core Modèle-Vue-Contrôleur).
Création du Modèle : Models/Voiture.cs
Dans le dossier Models, créez le fichier Voiture.cs. Chaque voiture possède un ID (Identifiant unique / Identifier), une marque, un modèle, une année de fabrication, un tarif journalier en DT (Dinars Tunisiens) et un statut de disponibilité :
namespace GestionVoitures.Models
{
public class Voiture
{
public int Id { get; set; }
public string Marque { get; set; } = string.Empty;
public string Modele { get; set; } = string.Empty;
public int Annee { get; set; }
public decimal PrixJour { get; set; }
public bool EstDisponible { get; set; }
}
}
Création du Contrôleur : Controllers/VoitureController.cs
Dans le dossier Controllers, créez le fichier VoitureController.cs contenant la liste statique en mémoire vive et l'ensemble des méthodes pour le CRUD (Create, Read, Update, Delete — Créer, Lire, Mettre à jour, Supprimer) :
using Microsoft.AspNetCore.Mvc;
using GestionVoitures.Models;
using System.Linq;
using System.Collections.Generic;
namespace GestionVoitures.Controllers
{
public class VoitureController : Controller
{
// 1. Liste STATIQUE en mémoire vive (RAM - Random Access Memory) partagée entre les requêtes
private static List<Voiture> _voitures = new List<Voiture>
{
new Voiture { Id = 1, Marque = "Toyota", Modele = "Yaris", Annee = 2022, PrixJour = 80.00m, EstDisponible = true },
new Voiture { Id = 2, Marque = "Renault", Modele = "Clio 5", Annee = 2023, PrixJour = 95.00m, EstDisponible = true },
new Voiture { Id = 3, Marque = "Volkswagen", Modele = "Golf 8", Annee = 2021, PrixJour = 140.00m, EstDisponible = false }
};
// ========================================================
// 1. AFFICHER LA LISTE DES VOITURES (READ)
// Requête HTTP GET: /Voiture ou /Voiture/Index
// ========================================================
public IActionResult Index()
{
return View(_voitures);
}
// ========================================================
// 2. AJOUTER UNE VOITURE (CREATE)
// ========================================================
// Requête HTTP GET: /Voiture/Create (Affiche le formulaire d'ajout vide)
[HttpGet]
public IActionResult Create()
{
return View();
}
// Requête HTTP POST: /Voiture/Create (Reçoit et enregistre la voiture soumise)
[HttpPost]
public IActionResult Create(Voiture nouvelleVoiture)
{
// Attribution automatique d'un nouvel identifiant (ID) unique
int nouvelId = _voitures.Any() ? _voitures.Max(v => v.Id) + 1 : 1;
nouvelleVoiture.Id = nouvelId;
// Ajout dans la liste statique en mémoire
_voitures.Add(nouvelleVoiture);
// Redirection vers l'affichage de la liste
return RedirectToAction(nameof(Index));
}
// ========================================================
// 3. MODIFIER UNE VOITURE (UPDATE)
// ========================================================
// Requête HTTP GET: /Voiture/Edit/2 (Affiche le formulaire pré-rempli avec la voiture existante)
[HttpGet]
public IActionResult Edit(int id)
{
var voiture = _voitures.FirstOrDefault(v => v.Id == id);
if (voiture == null)
{
return NotFound(); // Erreur 404 si la voiture n'existe pas
}
return View(voiture);
}
// Requête HTTP POST: /Voiture/Edit/2 (Valide et applique les modifications saisies)
[HttpPost]
public IActionResult Edit(Voiture voitureModifiee)
{
var voitureExistante = _voitures.FirstOrDefault(v => v.Id == voitureModifiee.Id);
if (voitureExistante == null)
{
return NotFound();
}
// Mise à jour des valeurs de l'objet existant
voitureExistante.Marque = voitureModifiee.Marque;
voitureExistante.Modele = voitureModifiee.Modele;
voitureExistante.Annee = voitureModifiee.Annee;
voitureExistante.PrixJour = voitureModifiee.PrixJour;
voitureExistante.EstDisponible = voitureModifiee.EstDisponible;
return RedirectToAction(nameof(Index));
}
// ========================================================
// 4. SUPPRIMER UNE VOITURE (DELETE)
// ========================================================
// Requête HTTP GET: /Voiture/Delete/3 (Affiche la page de confirmation de suppression)
[HttpGet]
public IActionResult Delete(int id)
{
var voiture = _voitures.FirstOrDefault(v => v.Id == id);
if (voiture == null)
{
return NotFound();
}
return View(voiture);
}
// Requête HTTP POST: /Voiture/Delete/3 (Effectue la suppression définitive dans la liste)
[HttpPost, ActionName("Delete")]
public IActionResult DeleteConfirmed(int id)
{
var voiture = _voitures.FirstOrDefault(v => v.Id == id);
if (voiture != null)
{
_voitures.Remove(voiture);
}
return RedirectToAction(nameof(Index));
}
}
}
Création des Vues Razor dans Views/Voiture/
Créez un nouveau sous-dossier nommé Voiture à l'intérieur du dossier Views, puis ajoutez-y les 4 fichiers de vues suivants :
Vue 1 : Views/Voiture/Index.cshtml (Tableau d'affichage)
@model IEnumerable<GestionVoitures.Models.Voiture>
@{
ViewData["Title"] = "Liste des Voitures";
}
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Catalogue des Voitures Disponibles</h2>
<a asp-action="Create" class="btn btn-primary">+ Ajouter une voiture</a>
</div>
<table class="table table-striped table-hover shadow-sm">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Marque</th>
<th>Modèle</th>
<th>Année</th>
<th>Prix / Jour</th>
<th>Disponibilité</th>
<th class="text-center">Actions</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>@item.Id</td>
<td><strong>@item.Marque</strong></td>
<td>@item.Modele</td>
<td>@item.Annee</td>
<td>@item.PrixJour.ToString("F2") DT</td>
<td>
@if (item.EstDisponible)
{
<span class="badge bg-success">Disponible</span>
}
else
{
<span class="badge bg-secondary">Louée</span>
}
</td>
<td class="text-center">
<a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-warning me-1">Modifier</a>
<a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-sm btn-danger">Supprimer</a>
</td>
</tr>
}
</tbody>
</table>
Vue 2 : Views/Voiture/Create.cshtml (Formulaire d'Ajout)
@model GestionVoitures.Models.Voiture
@{
ViewData["Title"] = "Ajouter une voiture";
}
<h2>Ajouter une nouvelle voiture</h2>
<hr />
<div class="row">
<div class="col-md-6">
<form asp-action="Create" method="post">
<div class="mb-3">
<label asp-for="Marque" class="form-label">Marque :</label>
<input asp-for="Marque" class="form-control" required placeholder="Ex: Hyundai" />
</div>
<div class="mb-3">
<label asp-for="Modele" class="form-label">Modèle :</label>
<input asp-for="Modele" class="form-control" required placeholder="Ex: i20" />
</div>
<div class="mb-3">
<label asp-for="Annee" class="form-label">Année :</label>
<input asp-for="Annee" type="number" class="form-control" value="2024" required />
</div>
<div class="mb-3">
<label asp-for="PrixJour" class="form-label">Tarif Journalier en Dinars (DT) :</label>
<input asp-for="PrixJour" type="number" step="0.01" class="form-control" required />
</div>
<div class="form-check mb-4">
<input asp-for="EstDisponible" class="form-check-input" type="checkbox" checked />
<label asp-for="EstDisponible" class="form-check-label">Véhicule disponible immédiatement</label>
</div>
<button type="submit" class="btn btn-success">Enregistrer la voiture</button>
<a asp-action="Index" class="btn btn-secondary ms-2">Annuler</a>
</form>
</div>
</div>
Vue 3 : Views/Voiture/Edit.cshtml (Formulaire de Modification)
@model GestionVoitures.Models.Voiture
@{
ViewData["Title"] = "Modifier une voiture";
}
<h2>Modifier la voiture #@Model.Id</h2>
<hr />
<div class="row">
<div class="col-md-6">
<form asp-action="Edit" method="post">
<!-- Champ caché pour transmettre l'ID de la voiture au contrôleur -->
<input type="hidden" asp-for="Id" />
<div class="mb-3">
<label asp-for="Marque" class="form-label">Marque :</label>
<input asp-for="Marque" class="form-control" required />
</div>
<div class="mb-3">
<label asp-for="Modele" class="form-label">Modèle :</label>
<input asp-for="Modele" class="form-control" required />
</div>
<div class="mb-3">
<label asp-for="Annee" class="form-label">Année :</label>
<input asp-for="Annee" type="number" class="form-control" required />
</div>
<div class="mb-3">
<label asp-for="PrixJour" class="form-label">Tarif Journalier (DT) :</label>
<input asp-for="PrixJour" type="number" step="0.01" class="form-control" required />
</div>
<div class="form-check mb-4">
<input asp-for="EstDisponible" class="form-check-input" type="checkbox" />
<label asp-for="EstDisponible" class="form-check-label">Disponible</label>
</div>
<button type="submit" class="btn btn-warning">Mettre à jour</button>
<a asp-action="Index" class="btn btn-secondary ms-2">Retour à la liste</a>
</form>
</div>
</div>
Vue 4 : Views/Voiture/Delete.cshtml (Confirmation de Suppression)
@model GestionVoitures.Models.Voiture
@{
ViewData["Title"] = "Supprimer une voiture";
}
<h2 class="text-danger">Confirmer la suppression</h2>
<p class="lead">Êtes-vous certain de vouloir supprimer ce véhicule du catalogue ?</p>
<div class="card p-4 col-md-6 border-danger mb-4 shadow-sm">
<dl class="row mb-0">
<dt class="col-sm-4">Identifiant (ID) :</dt>
<dd class="col-sm-8">@Model.Id</dd>
<dt class="col-sm-4">Véhicule :</dt>
<dd class="col-sm-8"><strong>@Model.Marque @Model.Modele</strong></dd>
<dt class="col-sm-4">Année :</dt>
<dd class="col-sm-8">@Model.Annee</dd>
<dt class="col-sm-4">Tarif Journalier :</dt>
<dd class="col-sm-8">@Model.PrixJour.ToString("F2") DT</dd>
</dl>
</div>
<form asp-action="Delete" method="post">
<input type="hidden" asp-for="Id" />
<button type="submit" class="btn btn-danger">Oui, Supprimer définitivement</button>
<a asp-action="Index" class="btn btn-secondary ms-2">Annuler</a>
</form>
Ajout du Lien dans la Navigation Globale : Views/Shared/_Layout.cshtml
Ouvrez le fichier Views/Shared/_Layout.cshtml et insérez un lien direct dans la barre de navigation <ul class="navbar-nav"> :
<li class="nav-item">
<a class="nav-link text-dark font-weight-bold" asp-controller="Voiture" asp-action="Index">
🚗 Gestion des Voitures
</a>
</li>
Lancement, Test et Observation Pédagogique
Lancez l'application depuis votre console :
dotnet run
Accédez à l'adresse indiquée dans votre navigateur (ex: http://localhost:5000/Voiture) et effectuez le parcours complet :
- ✅ Vérifier la liste : Observer les 3 voitures initiales du catalogue.
- ✅ Ajouter une voiture : Remplir le formulaire (ex: "Kia Rio", 2023, 90 DT) et constater son ajout immédiat dans le tableau.
- ✅ Modifier : Changer le prix ou la disponibilité d'une voiture.
- ✅ Supprimer : Supprimer une voiture et vérifier sa disparition.
dotnet run, toutes vos modifications s'effacent et la liste revient à son état initial de 3 voitures. Pourquoi ? Parce que la liste est conservée uniquement dans la mémoire vive volatile (RAM — Random Access Memory). Cela prouve l'utilité indispensable de la persistance permanente en base de données avec EF Core (Entity Framework Core) qui sera étudiée dans les chapitres suivants !
2.9 Synthèse du Chapitre — Les 6 Piliers à Retenir
1. Écosystème .NET Moderne
.NET (Network Enabled Technologies) est aujourd'hui une plateforme unifiée, open-source, multiplateforme (Linux/macOS/Windows) offrant d'excellentes performances grâce au serveur Kestrel.
2. Compilation C# (C-Sharp)
Le code C# est compilé en IL (Intermediate Language), puis traduit en code machine natif à l'exécution par le JIT (Just-In-Time) sous la supervision du GC (Garbage Collector).
3. Collections Indispensables
Utilisez List<T> pour les listes dynamiques indispensables du web et Dictionary<TKey, TValue> pour les recherches ultra-rapides en temps constant O(1).
4. Patron MVC
Séparation en trois rôles : le Modèle (Model) structure les données, le Contrôleur (Controller) traite la requête HTTP, et la Vue (View) Razor génère le HTML5 pour l'utilisateur.
5. ORM avec EF Core
L'ORM (Object-Relational Mapping) EF Core (Entity Framework Core) remplace le code SQL manuel par des requêtes LINQ (Language Integrated Query) typées et protégées contre les failles d'injection.
6. Cycle CRUD
Les opérations CRUD (Create, Read, Update, Delete) s'articulent autour d'actions HTTP GET (pour charger l'interface utilisateur) et HTTP POST (pour enregistrer les données soumises).