Aller au contenu principal

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érationHTTPActionVue
CreateGET + POSTCreate() + Create(article)Create.cshtml
Read (liste)GETIndex()Index.cshtml
Read (détail)GETDetails(id)Details.cshtml
UpdateGET + POSTEdit(id) + Edit(id, article)Edit.cshtml
DeleteGET + POSTDelete(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() vs _context.Entry().State

_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));
}
Toujours confirmer avant de supprimer

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));
}
}
astuce

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


Combien d'actions MVC nécessite généralement une opération de suppression CRUD ?


Quelle méthode EF Core met à jour un enregistrement existant ?


Pourquoi l'action Delete POST s'appelle DeleteConfirmed (et non Delete) ?


Quelle méthode EF Core supprime un enregistrement ?


Quel champ caché dans le formulaire d'édition permet de transmettre l'Id au POST ?


Quel attribut indique qu'une méthode répond à l'URL de l'action 'Delete' même si elle s'appelle 'DeleteConfirmed' ?


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


Bonne pratique - Vérifier l'existence avant d'afficher le formulaire

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


Bonne pratique - Inclure l'Id dans le Bind

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


Bonne pratique - Page de confirmation avant 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.

📌 Une solution