Aller au contenu principal

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 MVCContrôleur API
Hérite deControllerControllerBase
Attribut(aucun)[ApiController]
RetourIActionResult (HTML)ActionResult<T> (JSON)
VuesOui (.cshtml)Non
RoutesPar 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 helperCode HTTPSignification
Ok(data)200Succès avec données
Created(uri, data)201Ressource créée
CreatedAtAction(...)201Créé avec URL de la ressource
NoContent()204Succès sans données
BadRequest()400Requête invalide
Unauthorized()401Non authentifié
Forbidden()403Non autorisé
NotFound()404Ressource 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();
}
Ne jamais exposer l'entité directement en API

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


Quelle classe de base utilise un contrôleur API REST (sans vues) en ASP.NET Core ?


Quel attribut identifie une classe comme contrôleur API et active des comportements automatiques (validation, source des paramètres...) ?


Quel code HTTP retourne CreatedAtAction() ?


Pourquoi utilise-t-on des DTO plutôt que d'exposer directement les entités EF Core dans une API ?


Quel package NuGet permet d'afficher l'interface Swagger UI pour tester l'API ?


Quel code HTTP retourne NoContent() après une suppression réussie ?


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


Bonne pratique - Dossier DTOs séparé

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)


Bonne pratique pour les API

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


Bonne pratique - Swagger uniquement en développement

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.

📌 Une solution