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