API REST avec ASP.NET Core
Notions théoriques
MVC vs API REST
Jusqu'ici, vous avez créé des contrôleurs MVC qui retournent des vues HTML. ASP.NET Core permet aussi de créer des API REST qui retournent du JSON — pour alimenter une application mobile, un front-end React/Vue.js, ou pour exposer des données à d'autres services.
| Contrôleur MVC | Contrôleur API | |
|---|---|---|
| Hérite de | Controller | ControllerBase |
| Attribut | (aucun) | [ApiController] |
| Retour | IActionResult (HTML) | ActionResult<T> (JSON) |
| Vues | Oui (.cshtml) | Non |
| Routes | Par convention | [Route("api/[controller]")] |
Créer un contrôleur API
// Controllers/Api/ArticlesApiController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MonBlog.Data;
using MonBlog.Models;
[ApiController]
[Route("api/[controller]")]
public class ArticlesApiController : ControllerBase
{
private readonly MonBlogContext _context;
public ArticlesApiController(MonBlogContext context)
{
_context = context;
}
// GET api/articlesapi
[HttpGet]
public async Task<ActionResult<IEnumerable<Article>>> GetArticles()
{
return await _context.Articles.ToListAsync();
}
// GET api/articlesapi/5
[HttpGet("{id}")]
public async Task<ActionResult<Article>> GetArticle(int id)
{
var article = await _context.Articles.FindAsync(id);
return article == null ? NotFound() : article;
}
// POST api/articlesapi
[HttpPost]
public async Task<ActionResult<Article>> PostArticle(Article article)
{
_context.Articles.Add(article);
await _context.SaveChangesAsync();
// Retourne 201 Created avec l'URL de la ressource créée
return CreatedAtAction(nameof(GetArticle), new { id = article.Id }, article);
}
// PUT api/articlesapi/5
[HttpPut("{id}")]
public async Task<IActionResult> PutArticle(int id, Article article)
{
if (id != article.Id) return BadRequest();
_context.Update(article);
await _context.SaveChangesAsync();
return NoContent(); // 204
}
// DELETE api/articlesapi/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteArticle(int id)
{
var article = await _context.Articles.FindAsync(id);
if (article == null) return NotFound();
_context.Articles.Remove(article);
await _context.SaveChangesAsync();
return NoContent(); // 204
}
}
Codes de retour HTTP
| Méthode helper | Code HTTP | Signification |
|---|---|---|
Ok(data) | 200 | Succès avec données |
Created(uri, data) | 201 | Ressource créée |
CreatedAtAction(...) | 201 | Créé avec URL de la ressource |
NoContent() | 204 | Succès sans données |
BadRequest() | 400 | Requête invalide |
Unauthorized() | 401 | Non authentifié |
Forbidden() | 403 | Non autorisé |
NotFound() | 404 | Ressource introuvable |
Sources des paramètres
// [FromRoute] : depuis l'URL /api/articles/5
public IActionResult Get([FromRoute] int id) { }
// [FromQuery] : depuis ?page=2&size=10
public IActionResult List([FromQuery] int page, [FromQuery] int size) { }
// [FromBody] : depuis le corps JSON de la requête
public IActionResult Post([FromBody] Article article) { }
// [ApiController] infère automatiquement [FromBody] pour les objets complexes
// et [FromRoute]/[FromQuery] pour les types simples — pas besoin de les écrire
DTO (Data Transfer Object)
Il est fortement recommandé de ne pas exposer directement l'entité dans l'API. Utilisez un DTO pour contrôler exactement quelles données sont exposées :
// DTOs/ArticleDto.cs — Ce que l'API expose
public class ArticleDto
{
public int Id { get; set; }
public string Titre { get; set; } = string.Empty;
public string ResumeCourt { get; set; } = string.Empty;
public DateTime DateCreation { get; set; }
// Pas de Contenu complet, pas de champs internes (AuteurId, EstPublie...)
}
// Dans le contrôleur :
[HttpGet]
public async Task<ActionResult<IEnumerable<ArticleDto>>> GetArticles()
{
return await _context.Articles
.Select(a => new ArticleDto
{
Id = a.Id,
Titre = a.Titre,
ResumeCourt = a.Contenu.Length > 150 ? a.Contenu.Substring(0, 150) + "..." : a.Contenu,
DateCreation = a.DateCreation,
})
.ToListAsync();
}
Exposer l'entité EF Core directement dans l'API est dangereux : vous pourriez involontairement exposer des données sensibles (mots de passe hachés, clés internes...) et créer des références circulaires JSON (Article → Auteur → Article → ...). Utilisez toujours un DTO.
Swagger — Documentation automatique
dotnet add package Swashbuckle.AspNetCore
// Program.cs
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Après builder.Build() :
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(); // Interface à http://localhost:5000/swagger
}
Swagger génère automatiquement une interface web pour tester toutes les routes de l'API.
Tester l'API avec curl
# GET
curl http://localhost:5000/api/articlesapi
# POST avec JSON
curl -X POST http://localhost:5000/api/articlesapi \
-H "Content-Type: application/json" \
-d '{"titre":"Test API","contenu":"Mon premier article via API","estPublie":true}'
# DELETE
curl -X DELETE http://localhost:5000/api/articlesapi/1
Exemple pratique
API REST complète pour les articles avec DTO et Swagger :
// Controllers/Api/ArticlesApiController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using MonBlog.Data;
using MonBlog.DTOs;
using MonBlog.Models;
[ApiController]
[Route("api/articles")]
[Produces("application/json")]
public class ArticlesApiController : ControllerBase
{
private readonly MonBlogContext _context;
public ArticlesApiController(MonBlogContext context) { _context = context; }
/// <summary>Liste tous les articles publiés.</summary>
[HttpGet]
public async Task<ActionResult<IEnumerable<ArticleDto>>> GetArticles()
{
return await _context.Articles
.Where(a => a.EstPublie)
.OrderByDescending(a => a.DateCreation)
.Select(a => new ArticleDto
{
Id = a.Id,
Titre = a.Titre,
DateCreation = a.DateCreation,
})
.ToListAsync();
}
/// <summary>Récupère un article par son identifiant.</summary>
[HttpGet("{id:int}")]
public async Task<ActionResult<ArticleDto>> GetArticle(int id)
{
var article = await _context.Articles.FindAsync(id);
if (article == null) return NotFound(new { message = $"Article {id} introuvable." });
return new ArticleDto
{
Id = article.Id,
Titre = article.Titre,
DateCreation = article.DateCreation,
};
}
/// <summary>Crée un nouvel article.</summary>
[HttpPost]
public async Task<ActionResult<ArticleDto>> PostArticle(CreateArticleDto dto)
{
var article = new Article
{
Titre = dto.Titre,
Contenu = dto.Contenu,
DateCreation = DateTime.UtcNow,
EstPublie = false,
};
_context.Articles.Add(article);
await _context.SaveChangesAsync();
var result = new ArticleDto { Id = article.Id, Titre = article.Titre, DateCreation = article.DateCreation };
return CreatedAtAction(nameof(GetArticle), new { id = article.Id }, result);
}
/// <summary>Supprime un article.</summary>
[HttpDelete("{id:int}")]
public async Task<IActionResult> DeleteArticle(int id)
{
var article = await _context.Articles.FindAsync(id);
if (article == null) return NotFound();
_context.Articles.Remove(article);
await _context.SaveChangesAsync();
return NoContent();
}
}
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Vous allez créer une API REST pour les articles de MonBlog et la tester avec Swagger.
Étape 1 — Créer le dossier DTOs et la classe ArticleDto
Placez les DTO dans un dossier DTOs/ séparé de Models/. Cette séparation distingue clairement les entités EF Core (modèle de données interne) des DTO (contrat de l'API publique). Si le schéma de la base de données change, vous pouvez adapter le DTO indépendamment.
Étape 2 — Créer l'action GET de l'API (liste des articles)
Utilisez ActionResult<T> (et non IActionResult) pour les actions d'API qui retournent des données. Cela permet à Swagger de déduire automatiquement le type de retour et de générer une documentation correcte. ActionResult<T> supporte aussi bien return Ok(data) que return NotFound().
Étape 3 — Configurer Swagger et tester l'API
Activez Swagger uniquement en environnement de développement (if (app.Environment.IsDevelopment())). En production, l'interface Swagger expose la structure de votre API à des personnes malveillantes et peut être un vecteur d'attaque.