Architecture : Services et Injection de Dépendance
Au début d’un projet ASP.NET Core, on a tendance à écrire toute la logique métier directement dans les méthodes des Contrôleurs. C’est ce qu’on appelle l’anti-pattern “Fat Controller”.
Pourquoi séparer le code ?
Les contrôleurs ont pour unique responsabilité de gérer le protocole HTTP :
- Recevoir la requête (et désérialiser le JSON).
- Valider les entrées basiques.
- Appeler la logique métier.
- Renvoyer une réponse HTTP appropriée (200 OK, 404 Not Found, etc.).
Ils ne devraient JAMAIS contenir :
- La logique de calcul (ex: formule du coût du reset).
- Les appels directs à la base de données (si possible).
- La logique de validation complexe (ex: vérifier si l’utilisateur a assez d’argent).
Cette séparation permet de :
- Tester la logique métier sans lancer un serveur web (Tests Unitaires).
- Réutiliser la logique métier ailleurs (ex: dans une tâche planifiée).
- Lire plus facilement le code.
Création d’un Service
Un service est une classe C# standard qui contient la logique métier.
Exemple : GameService.cs
public class GameService
{
private readonly AppDbContext _context;
public GameService(AppDbContext context)
{
_context = context;
}
public void Click(int userId)
{
var progression = _context.Progressions.FirstOrDefault(p => p.UserId == userId);
if (progression == null)
{
throw new Exception("Progression introuvable");
}
progression.Count += (int)(1 * Math.Pow(1.5, progression.Multiplier));
_context.SaveChanges();
}
}
Injection de Dépendance (DI)
Pour utiliser ce service dans un contrôleur, il faut l’enregistrer dans le conteneur de dépendances.
Dans Program.cs :
// Enregistrement du service
// AddScoped : Une nouvelle instance est créée pour chaque requête HTTP
builder.Services.AddScoped<GameService>();
Ensuite, on l’injecte dans le constructeur du contrôleur :
[ApiController]
[Route("api/[controller]")]
public class GameController : ControllerBase
{
private readonly GameService _gameService;
public GameController(GameService gameService)
{
_gameService = gameService;
}
[HttpPost("click")]
public IActionResult Click()
{
// On récupère l'ID de l'utilisateur (via le token JWT par exemple)
int userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
try
{
_gameService.Click(userId);
return Ok();
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
}
Refactoring étape par étape
- Identifiez un bloc de logique dans votre contrôleur.
- Créez une méthode dans un Service correspondant (
UserService,GameService,InventoryService). - Déplacez le code (et les dépendances comme le
DbContext) dans ce Service. - Appelez le Service depuis le Contrôleur.
Votre contrôleur va maigrir et devenir beaucoup plus clair !