Créer une Web API avec les Minimal APIs
Introduction
ASP.NET Core propose plusieurs approches pour développer des services Web API. Dans ce chapitre, nous avons implémenté un service Web API de manière traditionnelle, via un contrôleur héritant de la classe ControllerBase, associé à des attributs tels que [ApiController], [Route], [HttpGet].
Depuis .NET 6, ASP.NET Core propose également les Minimal APIs. Elles permettent de déclarer directement les endpoints d'une API avec un nombre réduit de classes, d'attributs et de code d'infrastructure.
Le terme Minimal ne doit toutefois pas être interprété comme une invitation à supprimer toute architecture. Une Minimal API bien conçue doit conserver une séparation claire entre la gestion des requêtes HTTP, la logique applicative et l'accès aux données.
Création d'une première Minimal API
Voici l'implémentation d'une simple Minimal API dans le fichier Program.cs :
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello ASP.NET Core!");
app.Run();
Après avoir construit l'application avec la méthode Build(), nous pouvons configurer la pipeline HTTP et déclarer les endpoints exposés par l'application.
Déclarer un endpoint avec MapGet
Voici l'endpoint présenté dans ce chapitre, implémenté sous la forme d'une Minimal API :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IProductService, ProductService>();
var app = builder.Build();
app.MapGet(
"/api/product/{id:long}",
(long id, IProductService productService) =>
{
var product = productService.GetById(id);
return product is null
? Results.NotFound()
: Results.Ok(product);
});
app.Run();
Dans cette implémentation, ASP.NET Core récupère automatiquement la valeur du paramètre id depuis la route. L'instance de IProductService est obtenue depuis le conteneur d'injection de dépendances, dans lequel le service a préalablement été enregistré.
L'objectif des Minimal APIs n'est cependant pas uniquement de réduire le nombre de lignes de code. Elles proposent un autre modèle de programmation pour construire des API HTTP avec ASP.NET Core : les endpoints et leurs handlers sont déclarés directement, sans nécessiter la création de contrôleurs.
ASP.NET Core exécute le délégué associé à l'endpoint. Si le produit existe, l'instruction Results.Ok(product) génère une réponse HTTP 200 (OK) dont le contenu est sérialisé au format JSON. Dans le cas contraire, l'instruction Results.NotFound() génère une réponse HTTP 404 (Not Found). Il n'est donc pas nécessaire de créer un contrôleur uniquement pour exposer cette opération.
Minimal APIs ne signifie pas « tout implémenter dans le fichier Program.cs »
L'une des critiques fréquemment formulées à propos des Minimal APIs concerne le risque de transformer le fichier Program.cs en un fichier contenant un nombre important d'endpoints.
Les endpoints peuvent être organisés dans différentes classes et regroupés par fonctionnalité en utilisant les méthodes d'extension. Voici la même Minimal API implémentée précédemment :
public static class ProductEndpoints
{
public static IEndpointRouteBuilder MapProductEndpoints(
this IEndpointRouteBuilder endpoints)
{
endpoints.MapGet(
"/api/product/{id:long}",
(long id, IProductService productService) =>
{
var product = productService.GetById(id);
return product is null
? Results.NotFound()
: Results.Ok(product);
});
return endpoints;
}
}
Le fichier Program.cs devient alors beaucoup plus simple :
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<IProductService, ProductService>();
var app = builder.Build();
app.MapProductEndpoints();
app.Run();
Nous retrouvons ainsi une organisation structurée tout en conservant le modèle de programmation proposé par les Minimal APIs.
Regrouper les endpoints avec la méthode MapGroup
Lorsque plusieurs endpoints concernent une même ressource, la méthode MapGroup permet de leur appliquer un préfixe commun :
var products = app.MapGroup("/api/product");
products.MapGet("/", GetProducts);
products.MapGet("/{id:long}", GetProduct);
products.MapPost("/", CreateProduct);
products.MapPut("/{id:long}", UpdateProduct);
products.MapDelete("/{id:long}", DeleteProduct);
Cette méthode évite de répéter le préfixe /api/product pour chaque endpoint. Les groupes permettent également de regrouper des endpoints appartenant à une même fonctionnalité et de leur appliquer une configuration commune, comme une politique d'autorisation avec la méthode RequireAuthorization().
Minimal APIs et contrôleurs : faut-il choisir ?
Les Minimal APIs ne remplacent pas nécessairement les contrôleurs. Les deux modèles permettent de construire des services Web API avec ASP.NET Core, mais proposent des organisations différentes.
Dans le précédent article de cette newsletter, nous avons implémenté ce service Web API dont l'implémentation correspond à la Minimal API présentée dans cet article :
[ApiController]
[Route("api/product")]
public class ProductController(IProductService productService) : ControllerBase
{
[HttpGet("{id:long}")]
public IActionResult Get(long id)
{
var product = productService.GetById(id);
return product is null
? NotFound()
: Ok(product);
}
}
Certes, son implémentation est plus verbeuse. Cependant, la différence ne se limite pas au nombre de lignes de code. Les contrôleurs proposent un modèle basé sur des classes, des méthodes et des attributs, tandis que les Minimal APIs reposent principalement sur la déclaration de endpoints et de handlers. Le choix doit donc dépendre de la taille de l'application, de son organisation, des conventions de l'équipe et des besoins du projet.
Voici un tableau qui présente les principales différences en terme d'implémentation entre ces deux modèles de développement :
| Critère | Minimal APIs | Contrôleurs |
|---|---|---|
| Modèle de programmation | Endpoints associés à des handlers | Classes héritant de ControllerBase et les méthodes d'action |
| Déclaration des routes | Méthodes MapGet(), MapPost(), MapPut(), etc. |
Attributs [HttpGet], [HttpPost], [Route], etc. |
| Organisation | Endpoints pouvant être regroupés avec MapGroup() et organisés avec des méthodes d'extension | Endpoints regroupés dans des classes Controller |
| Injection de dépendances | Services injectés dans les paramètres du handler | Services injectés dans le constructeur du contrôleur |
| Binding | Binding des paramètres du handler depuis route, query string, body, headers, services, etc. | Model Binding MVC avec notamment [FromRoute], [FromQuery], [FromBody], etc. |
| Validation | Validation native avec DataAnnotations depuis .NET 10 via AddValidation() | Validation intégrée au modèle MVC avec [ApiController] |
| Filtres | Utilisation des Endpoint Filters | Filtres MVC : Action, Exception, Result, Authorization, etc. |
| Configuration commune | Configuration applicable aux endpoints et groupes d'endpoints | Attributs et conventions applicables aux contrôleurs et leurs actions |
Quand utiliser les Minimal APIs ?
Les Minimal APIs sont particulièrement adaptées aux API HTTP pour lesquelles on souhaite un modèle de programmation centré sur les endpoints. Elles conviennent très bien aux microservices et aux API simples, mais elles peuvent aussi être utilisées dans des applications plus importantes, à condition de structurer correctement les endpoints, par exemple avec des classes dédiées, des méthodes d'extension et des groupes de routes.
Les contrôleurs restent quant à eux parfaitement adaptés aux applications pour lesquelles le modèle MVC et l'organisation par contrôleurs sont utiles.