Clean Architecture .NET API - Partie 2 - Mise en place de l’API

Après avoir créé, structuré et posé les bases Clean Architecture .NET, il est temps de passer à l’API, pas à pas, c’est parti !
alexJournet.jpg
Alexandre JOURNETMis à jour le 3 Nov 2023
Clean Architecture .NET API - Partie 1 - Création, structure et base - tuto mise en place de l'API

Après avoir créé, structuré et posé les bases Clean Architecture .NET, il est temps de passer à l’APIUne API est un programme permettant à deux applications distinctes de communiquer entre elles et d’échanger des données., pas à pas, c’est parti !

Pour retrouver la 1ère partie de ce tuto, RDV par ici.

On va créer une API qui va nous permettre de récupérer un mot ou une liste de mots :

Création du repository et service associés

Pour commencer, nous allons implémenter tous les fichiers relatifs à notre “Mot” :

Mot.cs → Projet Domain, dossier Models

public class Mot : BaseEntity
{
        public string Text { get; set; }
}

On va créer les interfaces ainsi que le repository et le service associé :

MotRepository.cs → Projet Infrastructure, dossier RepositoryC'est l'emplacement où Git va stocker toutes les informations permettant de gérer le versioning et l'historisation d'une application.

public class MotRepository : BaseRepository<Mot, CoreDbContext>
{
        public MotRepository(CoreDbContext databaseContext) : base(databaseContext)
        {
        }
}

MotService.cs → Projet ApplicationC'est un programme conçu pour effectuer une ou plusieurs tâches. Réaliser des applications, c'est notre cœur de métier chez AXOPEN !, dossier Service

public class MotService : BaseService<IMotRepository, Mot>
{
        public MotService(MotRepository repository) : base(repository)
        {
        }
}

Avant de pouvoir utiliser nos services fraîchement créés dans nos contrôleurs, nous allons devoir les déclarer pour pouvoir effectuer l’injection de dépendances.

Pour ce faire, nous allons créer un fichier DependencyInjection à la racine du projet Infrastructure ainsi qu’à la racine du projet Applications :

DependencyInjection.cs → Projet Infrastructure

public static class DependencyInjection
{
        public static void AddRepositories(this IServiceCollection services)
        {
            //Ajouter les repository
            services.AddScoped<MotRepository>();
        }
}

DependencyInjection.cs → Projet Application

public static class DependencyInjection
{

        public static void AddServices(this IServiceCollection services)
        {
            //Ajouter les services
            services.AddScoped<MotService>();
        }
}

TIPS : Vous allez forcément avoir une application de plus en plus grosse. Dans ce cas, il faut automatiser ces imports :

Voici une fonction qui permet de récupérer tous les fichiers finissant par Repository, dans le namespace Repository de notre projet Infrastructure et d’automatiquement signaler à notre API de les enregistrer :

public static void AddRepositories(this IServiceCollection services)
{
            Assembly assembly = Assembly.GetExecutingAssembly();

            assembly.GetTypes().Where(t => $"{assembly.GetName().Name}.Repository" == t.Namespace
                                        && !t.IsAbstract
                                        && !t.IsInterface
                                        && t.Name.EndsWith("Repository"))
            .Select(a => new { assignedType = a, serviceTypes = a.GetInterfaces().ToList() })
            .ToList()
            .ForEach(typesToRegister =>
            {
                typesToRegister.serviceTypes.ForEach(typeToRegister => services.AddScoped(typeToRegister, typesToRegister.assignedType));
            });
}

Pour la partie Services, il suffit de remplacer “Repository” par “Service” et vous aurez vos injections automatiques !

Après pour faire encore plus proprement, on peut passer par un “Attribute”, récupérer tous les types qui ont cette annotation et les injecter.

Grâce à ces fichiers, on va pouvoir déclarer tous nos repository et services en une seule ligne de code :

.NET 6 : dans le fichier Program.cs juste après l’initialisation du builder :

// Add services to the container.
builder.Services.AddServices();
builder.Services.AddRepositories();

.NET 5 et inférieur, cela se situe dans le fichier Startup.cs, dans la méthode ConfigureServices(IServiceCollection services) dans laquelle on va pouvoir rajouter :

services.AddServices();
services.AddRepositories();

Création de la configuration

On rajoute le modèle créé à notre dbContext :

public class CoreDbContext : DbContext
{

        public CoreDbContext(DbContextOptions<CoreDbContext> options) : base(options)
        { }

        public DbSet<Mot> Mot { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
        }
}

Ici, on va créer un dossier EntityConfigurations dans le Projet Infrastructure et dans le dossier Database :

MotConfiguration.cs → Projet Infrastructure, dossier Database puis EntityConfigurations

public class MotConfiguration : IEntityTypeConfiguration<Mot>
{
        public void Configure(EntityTypeBuilder<Mot> builder)
        {
            builder.ToTable(nameof(Mot));
            builder.HasKey(c => c.Id).HasAnnotation("DatabaseGenerated", DatabaseGeneratedOption.Identity);
        }
}

Cela va permettre à EF de créer correctement notre modèle lors de notre première migration.

Il ne faut pas oublier de lui signaler ce fichier :

public class CoreDbContext : DbContext
{

        public CoreDbContext(DbContextOptions<CoreDbContext> options) : base(options)
        { }

        public DbSet<Mot> Mot { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
        }
}

ApplyConfigurationsFromAssembly est une méthode du ModelBuilder, qui va nous permettre d’appliquer automatiquement toutes les configurations à l’intérieur de notre assembly, ici le projet Infrastructure.

Création de notre API

Finalisons la création de notre API !

MotController.cs → Projet API, dossier Controller

public class MotController : ControllerBase
{
        IMotService _motService;

        public MotController(IMotService motService)
        {
            _motService = motService;
        }

        [HttpGet]
        public async Task<ActionResult> GetAll()
        {
            try
            {
                ActionResult result = StatusCode(500);
                List<Mot> mots = await _motService.GetAllAsync();
                if(mots.IsNotEmpty())
                {
                    result = Ok(mots);
                }
                return result;
            }
            catch (Exception e)
            {
                return StatusCode(500, e.StackTrace);
            }
        }

        [HttpGet]
        [Route("{id}")]
        public async Task<ActionResult> GetById()
        {
            try
            {
                ActionResult result = StatusCode(500);
                Mot mot = await _motService.GetById(1);
                if (mot != null)
                {
                    result = Ok(mot);
                }
                return result;
            }
            catch (Exception e)
            {
                return StatusCode(500, e.StackTrace);
            }
        }
}

J’ai créé une extension pour les listes pour implémenter la méthode isNotEmpty() :

ListExtensions.cs → Projet Helpers, dossier Extensions

public static class ListExtensions
{
        public static bool IsNotEmpty<T>(this IList<T> list)
        {
            return list != null && list.Count > 0;
        }
}

Et voilà !

Il vous suffit d’initialiser votre base de données et de créer la première migration afin de faire fonctionner votre API !