CRUD complet
Notions théoriques
Les quatre opérations CRUD
CRUD (Create, Read, Update, Delete) représente les quatre opérations fondamentales sur les données. En ASP.NET Core MVC, chaque opération correspond à une ou deux actions du contrôleur :
| Opération | HTTP | Action | Vue |
|---|---|---|---|
| Create | GET + POST | Create() + Create(article) | Create.cshtml |
| Read (liste) | GET | Index() | Index.cshtml |
| Read (détail) | GET | Details(id) | Details.cshtml |
| Update | GET + POST | Edit(id) + Edit(id, article) | Edit.cshtml |
| Delete | GET + POST | Delete(id) + DeleteConfirmed(id) | Delete.cshtml |
L'action Edit (GET + POST)
// GET /Articles/Edit/5 — Affiche le formulaire pré-rempli
public async Task<IActionResult> Edit(int? id)
{
if (id == null) return NotFound();
var article = await _context.Articles.FindAsync(id);
if (article == null) return NotFound();
return View(article); // Passe l'article au formulaire
}
// POST /Articles/Edit/5 — Enregistre les modifications
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Titre,Contenu,EstPublie")] Article article)
{
if (id != article.Id) return NotFound();
if (ModelState.IsValid)
{
_context.Update(article); // Marque l'entité comme modifiée
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(article);
}
_context.Update(article) marque toutes les propriétés de l'entité comme modifiées (UPDATE avec toutes les colonnes). Pour ne mettre à jour que certaines colonnes, utilisez :
_context.Entry(article).Property(a => a.Titre).IsModified = true;
L'action Delete (GET + POST)
La suppression utilise deux actions :
- GET : affiche une page de confirmation ("Êtes-vous sûr ?")
- POST : effectue réellement la suppression
// GET /Articles/Delete/5 — Page de confirmation
public async Task<IActionResult> Delete(int? id)
{
if (id == null) return NotFound();
var article = await _context.Articles.FindAsync(id);
if (article == null) return NotFound();
return View(article);
}
// POST /Articles/Delete/5 — Suppression effective
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var article = await _context.Articles.FindAsync(id);
if (article != null)
{
_context.Articles.Remove(article);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Index));
}
Une page de confirmation protège contre les suppressions accidentelles. N'effectuez jamais une suppression via une requête GET (un lien <a>) — un robot d'indexation ou un utilisateur distrait pourrait déclencher des suppressions non intentionnelles.
Tag Helpers pour les formulaires CRUD
<!-- Formulaire d'édition -->
<form asp-action="Edit" asp-route-id="@Model.Id" method="post">
<input type="hidden" asp-for="Id" />
<input asp-for="Titre" class="form-control" />
<button type="submit">Enregistrer</button>
</form>
<!-- Formulaire de suppression -->
<form asp-action="Delete" asp-route-id="@Model.Id" method="post">
<button type="submit" class="btn btn-danger">Supprimer</button>
</form>
Exemple pratique
Contrôleur CRUD complet pour les articles de MonBlog :
// Controllers/ArticlesController.cs — CRUD complet
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MonBlog.Data;
using MonBlog.Models;
public class ArticlesController : Controller
{
private readonly MonBlogContext _context;
public ArticlesController(MonBlogContext context)
{
_context = context;
}
// GET /Articles
public async Task<IActionResult> Index()
{
return View(await _context.Articles.OrderByDescending(a => a.DateCreation).ToListAsync());
}
// GET /Articles/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null) return NotFound();
var article = await _context.Articles.FirstOrDefaultAsync(a => a.Id == id);
return article == null ? NotFound() : View(article);
}
// GET /Articles/Create
public IActionResult Create() => View();
// POST /Articles/Create
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Titre,Contenu,EstPublie")] Article article)
{
if (ModelState.IsValid)
{
article.DateCreation = DateTime.UtcNow;
_context.Add(article);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(article);
}
// GET /Articles/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null) return NotFound();
var article = await _context.Articles.FindAsync(id);
return article == null ? NotFound() : View(article);
}
// POST /Articles/Edit/5
[HttpPost, ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id,
[Bind("Id,Titre,Contenu,EstPublie")] Article article)
{
if (id != article.Id) return NotFound();
if (ModelState.IsValid)
{
_context.Update(article);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(article);
}
// GET /Articles/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null) return NotFound();
var article = await _context.Articles.FindAsync(id);
return article == null ? NotFound() : View(article);
}
// POST /Articles/Delete/5
[HttpPost, ActionName("Delete"), ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var article = await _context.Articles.FindAsync(id);
if (article != null)
{
_context.Articles.Remove(article);
await _context.SaveChangesAsync();
}
return RedirectToAction(nameof(Index));
}
}
Visual Studio et VS Code proposent des scaffolders (générateurs de code) qui créent automatiquement le CRUD complet (contrôleur + 5 vues) à partir d'une entité. C'est utile pour un prototype rapide, mais les fichiers générés doivent toujours être relus et personnalisés.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Vous allez compléter le CRUD d'articles en implémentant les actions Edit et Delete.
Étape 1 — Implémenter l'action GET Edit
Toujours vérifier que l'objet existe avant d'afficher le formulaire d'édition. Si un utilisateur accède à /Articles/Edit/999 et que l'article 999 n'existe pas, retournez un 404 clair plutôt que de lancer une exception NullReferenceException.
Étape 2 — Implémenter l'action POST Edit
Dans l'action Edit POST, incluez "Id" dans le [Bind("Id,Titre,Contenu")]. Sans l'Id, EF Core ne sait pas quelle ligne UPDATE. Mais ne l'incluez pas dans le Create POST — cela évite qu'un utilisateur malveillant spécifie l'Id d'un autre enregistrement.
Étape 3 — Implémenter la suppression
La vue Delete.cshtml doit afficher un résumé de l'objet à supprimer et demander confirmation. Une suppression sans confirmation est une faute d'ergonomie et peut entraîner des pertes de données irréversibles.