Accueil Nos publications Blog Comment organiser votre Startup.cs avec les méthodes d’extension ?

Comment organiser votre Startup.cs avec les méthodes d’extension ?

header politique handicap

Comment organiser votre Startup.cs avec les méthodes d’extension ?

Si vous êtes un·e développeur·se Asp.Net core, j’imagine que vous êtes familier·ère avec le cauchemar du fichier Startup.cs.

À mesure que votre projet grandit, ce fichier passe très rapidement à une centaine de lignes vu qu’il peut contenir, entre autres, des configurations, des déclarations de middleware, des définitions d’injection de dépendance ou le paramétrage de l’authentification 🤦‍.

Si toute la configuration de notre application, notamment la partie injection de dépendance, se trouve dans la classe Startup c’est un fichier qui va être édité particulièrement souvent ce qui peut être source de conflit lors de merge de branche.

Bien organiser son Startup.cs est un bon réflexe à adopter, débutants comme expérimentés, pour montrer que vous faites attention à la qualité de votre code.

Dans cet article on va apprendre comment utiliser “les méthodes d’extension” pour bien structurer nos projets Asp.net core et éviter d’avoir un fichier Startup.cs illisible dans lequel on scrolle à l’infini, le tout dans une logique de “Clean Code”.

Les méthodes d’extension à la rescousse

Les méthodes d’extension (introduites avec C# 3.0) vous permettent d’ajouter des méthodes à des types pré-existants sans créer des types dérivés ou modifier le type d’origine.

En exposant des méthodes d’extension utilisables dans la classe Startup, l’usage des méthodes d’extension est dans la lignée dans ce que fait le framework .net core.
Ex: AddAuthentication, AddControllers ou également UseAuthorization, UseRouting, etc.

Pour vous donner une idée de leur fonctionnement, on va faire un petit exemple :
On va étendre la class String avec une nouvelle méthode nommée CountChar(char c) qui retourne le nombre d’occurrence d’un caractère dans un String.

On aura besoin de :

  • Une classe statique dans laquelle on va écrire notre méthode d’extension
  • Une méthode statique avec une signature un peu spéciale en utilisant le mot clé this avant le type auquel on va ajouter la méthode.
namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int CountChar(this String str,Char character)
        {
            return str.Count(x=> x == character); 
        }
    }
}

Pour utiliser cette méthode dans notre code, il suffit d’importer le namespace ExtensionMethods dans notre fichier, et voilà !
La magie d’IntelliSense se charge du reste ✨✨. À partir de maintenant, on aura une suggestion avec le nom de notre méthode sur tous les objets de type String.

Vous pouvez voir plus d’exemples sur les méthodes d’extension dans la doc officielle ici.

Place à l’action

Maintenant que l’on connaît le principe, on peut attaquer notre problématique initiale : organiser notre fichier Startup.cs.

 

Mais comment on va faire ça ?

On va prendre comme exemple le code d’une configuration de Swagger que l’on trouve dans la majorité des APIs ASP.net core et le refactorer.

Si on analyse un peu le code à ajouter (documentation) on remarque qu’on est en train de manipuler deux objets :

  1. service qui est une instance de IServiceCollection qui sert à configurer le conteneur IoC
  2. app qui est une instance de IApplicationBuilder qui sert à configurer l’application et ses middlewares

Donc on va ajouter deux nouvelles méthodes à IServiceCollection et IApplicationBuilder méthodes en utilisant les extensions

Comme l’exemple ci-dessus on aura besoin :

  • D’une classe statique dans laquelle on va écrire notre méthode d’extension ;
  • D’une ou plusieurs méthodes statiques avec une signature un peu spéciale en utilisant le mot clé this avant le type dans lequel on va ajouter la méthode.
namespace ExtensionMethods
{

    public static class SwaggerConfigurationExtension
    {

        public static void AddSwaggerConfig(this IServiceCollection services)
        {

          services.AddSwaggerGen(c =>
            {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
            });
        }

        public static void UseCustomSwaggerConfig(this IApplicationBuilder app)
        {

            app.UseSwagger();

            app.UseSwaggerUI(c =>

            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
            });

        }
    }
}

Dans le code ci-dessous, nous avons créé deux méthodes d’extensions :

  • AddSwaggerConfig() ajoutée à IServiceCollection pour faire la configuration du générateur swagger.
  • UseCustomSwaggerConfig() ajoutée à IApplicationBuilder pour ajouter les middlewares swagger générateur et UI.

GIF Animé

Refactoring du Startup.cs

On aura un fichier startup qui ressemble à ça :

    public class Startup
    {

         public void ConfigureServices(IServiceCollection services)
        {
                services.AddSwaggerConfig();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
                app.UseCustomSwaggerConfig();
        }
    }

 

Organisation et découpage

Dans cet article, on a vu un exemple de découpage technique dans lequel on a regroupé toutes les configs liées à Swagger dans une seule classe.

Dans le contexte d’un gros projet, il est recommandé d’éviter un découpage technique et aller plutôt vers une approche fonctionnelle les regroupant par “Feature”.

Regrouper sous des méthodes d’extensions techniques semble au premier abord une bonne idée, mais d’expérience on se rend compte rapidement que nous avons juste déporté le problème.
En effet, si on prend par exemple une méthode AddRepositories qui contient I’enregistrement dans Ie conteneur IOC de l’ensemble de nos repositories, au fur et à mesure que le projet grossit, le nombre de repositories grossit également et le nombre de lignes de cette classe explose pour en revenir à Ia problématique initiale du Startup.

Un découpage basé sur les Bounded Context dans une approche DDD (Domain Driven Design) constitue une alternative intéressante à un découpage technique, vu que celle-ci nous permet d’avoir un découpage fonctionnel proche métier, et ça réduira aussi les conflits dans les phases de développement car 2 développements de 2 fonctionnalités différentes se feront sur 2 bases de code cloisonnées.

Comme ça on peut avoir une organisation modulaire et des projets réutilisables voir packageables autour de chaque bounded context.

Un petit exemple pour illustrer cette approche :

 public class Startup
    {
         public void ConfigureServices(IServiceCollection services)
        {
                services.AddCatalog();
                services.AddOrderFlow();
               ...
        }
    }

AddCatalog(): Extension globale pour ajouter toutes les extensions du Catalogue

namespace WebApp.Catalog.Extensions
{
    public static class CatalogExtensions
    {
       public static void AddCatalog(this IServiceCollection services)
        {
          services.AddProducts();
          services.AddPrices();
        }
    }
}

AddProducts(): Extension globale pour ajouter toutes les extensions liées aux produits

namespace WebApp.Catalog.Extensions
{
    public static class ProductsExtensions
    {
        public static void AddProducts(this IServiceCollection services)
        {
          services.AddProductsRepositories();
          services.AddProductsServices();
          ....
        }
    }
}

AddProductsRepositories(): Extension pour ajouter la couche repository liée aux produits
AddProductsServices(): Extension pour ajouter la couche Services liée aux produits

J’espère que cet article vous a apporté un plus🤜🤛

Avant de finir, je vous invite à méditer cette citation de Martin Fowler, l’un des pionniers du mouvement Clean Code :

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” — Martin Fowler


Liens utiles

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods

https://docs.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle?view=aspnetcore-3.1&tabs=visual-studio

https://martinfowler.com/