Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 :

  1. Recevoir la requête (et désérialiser le JSON).
  2. Valider les entrées basiques.
  3. Appeler la logique métier.
  4. 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

using GameServerApi.Models;
using Microsoft.EntityFrameworkCore;

namespace GameServerApi.Services;

public class GameService
{
    private readonly BDDContext _context;

    public GameService(BDDContext context)
    {
        _context = context;
    }

    public async Task<Progression> ClickAsync(int userId)
    {
        var progression = await _context.Progressions.FirstOrDefaultAsync(p => p.UserId == userId);
        if (progression == null) 
        {
            throw new Exception("Progression introuvable");
        }

        progression.Count += (1 + progression.TotalClickValue) * progression.Multiplier;
        await _context.SaveChangesAsync();
        return progression;
    }
}

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 :

using System.Security.Claims;
using GameServerApi.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace GameServerApi.Controllers;

[Authorize]
[ApiController]
[Route("api/[controller]")]
public class GameController : ControllerBase
{
    private readonly GameService _gameService;

    public GameController(GameService gameService)
    {
        _gameService = gameService;
    }

    [HttpGet("Click")]
    public async Task<IActionResult> Click()
    {
        var idClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (!int.TryParse(idClaim, out int userId)) return Unauthorized();

        var progression = await _gameService.ClickAsync(userId);
        return Ok(new
        {
            count = progression.Count,
            multiplier = progression.Multiplier,
            totalClickValue = progression.TotalClickValue
        });
    }
}

Refactoring étape par étape

  1. Identifiez un bloc de logique dans votre contrôleur.
  2. Créez une méthode dans un Service correspondant (UserService, GameService, InventoryService).
  3. Déplacez le code (et les dépendances comme le DbContext) dans ce Service.
  4. Appelez le Service depuis le Contrôleur.

Votre contrôleur va maigrir et devenir beaucoup plus clair !