Fermer

décembre 17, 2023

Bases d’ASP.NET Core : Comprendre l’injection de dépendances

Bases d’ASP.NET Core : Comprendre l’injection de dépendances


Découvrez ce qui se passe si vous n’utilisez pas l’injection de dépendances, puis passez aux meilleures pratiques en matière de DI, IoC et DIP.

L’injection de dépendances (DI) est un modèle de conception largement utilisé dans le développement de logiciels, et sa compréhension est une exigence de base pour toute personne souhaitant travailler avec le développement Web dans ASP.NET Core. Le but de DI est de promouvoir la modularité, la réutilisabilité et la testabilité du code. Dans ASP.NET Core en particulier, DI joue un rôle crucial dans la création d’applications robustes et évolutives.

Cet article explique simplement le concept de DI, décrit les relations entre DI, Inversion de contrôle (IoC), principe d’inversion de dépendance (DIP) et Service Locator, et montre comment implémenter DI, avec des exemples de code.

Qu’est-ce que l’injection de dépendances et quels sont ses avantages ?

DI est un concept qui permet à des acteurs externes, tels que des paramètres de construction, des propriétés ou des méthodes de configuration, de fournir des dépendances d’une classe plutôt que de les créer au sein de la classe. Cela réduit le couplage entre les composants du système, rendant le code plus stable et modulaire.

L’injection de dépendances dans ASP.NET Core vous offre plusieurs avantages, notamment :

  • Découplage : DI réduit le couplage entre les composants de l’application, leur permettant d’être modifiés indépendamment, facilitant ainsi la maintenance et l’évolution du système.
  • Réutilisation du code : DI permet le partage de composants, ce qui favorise la réutilisation du code et évite les duplications inutiles.
  • Testabilité : DI facilite l’écriture de tests unitaires, car il permet de remplacer facilement les dépendances par des simulations ou de fausses implémentations lors de la construction et de l’exécution des tests.

Le schéma ci-dessous montre comment ASP.NET Core gère DI.

di-schéma

S’entraîner avec DI

Créons ensuite une application simple dans ASP.NET Core pour montrer comment implémenter DI. Ensuite, nous examinerons le même exemple, mais sans injection de dépendances et verrons quels problèmes cela peut entraîner.

Conditions préalables

Pour créer l’exemple dans cet article, vous devez installer le SDK .NETversion 7 ou ultérieure.

Vous avez également besoin d’un terminal pour exécuter les commandes .NET. Vous pouvez les exécuter directement depuis un IDE si vous préférez ; cet exemple utilise Visual Studio Code.

Vous pouvez accéder au code source des exemples de cet article sur GitHub.

Pour créer l’application de base, exécutez la commande suivante dans le terminal :

dotnet new web -o ContactRegister

Ouvrez le projet nouvellement créé avec votre IDE préféré. Dans le projet, créez un nouveau dossier appelé Models et à l’intérieur, créez un nouveau fichier appelé Contact.cs. Remplacez le code existant par le code ci-dessous :

namespace ContactRegister.Models;

public record Contact(Guid Id, string Name, string Email, string PhoneNumber);

Créez un nouveau dossier appelé Data et à l’intérieur, créez une nouvelle interface appelée IContactRepository.cs. Mettez-y le code ci-dessous :

using ContactRegister.Models;

namespace ContactRegister.Repository;

public interface IContactRepository{
  public List<Contact> FindContacts();
}

Toujours à l’intérieur du Data dossier, créez une nouvelle classe appelée ContactRepository.cs et mettez-y le code ci-dessous :

using ContactRegister.Models;

namespace ContactRegister.Repository;

public class ContactRepository : IContactRepository
{
  public List<Contact>  FindContacts(){
    var contacts = new List<Contact>(){
      new Contact(Guid.NewGuid(), "John Smith", "jsmith@examplemail.com", "987654321"),
      new Contact(Guid.NewGuid(), "Amy Davis", "amy@examplemail.com", "987654321")
     };
     return contacts;
  }
}

Notez que dans le code précédent, vous avez créé un enregistrement pour représenter l’entité de contact, puis vous avez créé une interface et une classe dotée d’une méthode qui renvoie une liste de contacts.

Maintenant, créez un nouveau dossier appelé Services. À l’intérieur, créez une nouvelle classe appelée ContactService.cs et mettez le code ci-dessous :

using ContactRegister.Repository;
using ContactRegister.Models;

namespace ContactRegister.Services;

public class ContactService {
  private readonly IContactRepository _repository;

  public ContactService(IContactRepository repository)
  {
    _repository = repository;
  }

  public List<Contact> FindAllContacts() =>
    _repository.FindContacts();
}

Notez que dans le code ci-dessus, pour utiliser le FindContacts() méthode du ContactRepository classe, vous injectez la dépendance via la déclaration de la classe private readonly IContactRepository _repository;. Vous réussissez alors le cours ContactRepository dans le constructeur de la classe ContactServiceainsi:

public ContactService(IContactRepository repository)
  {
    _repository = repository;
  }

De cette façon, chaque fois que le ContactService la classe est instanciée, une nouvelle instance de la ContactRepository la classe sera créée et sera disponible pour utilisation.

Implémentation de l’IoC

Dans ASP.NET Core, l’inversion de contrôle (IoC) est un modèle de conception dans lequel la responsabilité de la création et de la gestion des objets est transférée à un conteneur IoC, plutôt que d’être contrôlée directement par le code de l’application.

IoC favorise le découplage et la modularité dans le développement d’applications. Plutôt qu’une classe dépendant directement d’autres classes ou instanciant directement des objets, elle déclare ses dépendances via des interfaces ou des classes de base abstraites. Le conteneur IoC est chargé de résoudre ces dépendances et de fournir les implémentations nécessaires.

Dans les versions plus récentes d’ASP.NET Core, vous pouvez configurer le conteneur IoC via le Program classe. Vous pouvez enregistrer les dépendances de votre application en utilisant le AddTransient, AddScoped et AddSingleton méthodes, en fonction du cycle de vie requis pour chaque service.

Le conteneur IoC gère la création de ces objets et s’assure que les dépendances sont correctement résolues. En utilisant IoC, nous déléguons la responsabilité de gérer le DI aux ressources natives ASP.NET Core plutôt que de le faire manuellement.

Pour implémenter IoC dans votre application, ajoutez le code ci-dessous dans le Program.cs déposer:

builder.Services.AddSingleton<IContactRepository, ContactRepository>();

Notez que dans le code ci-dessus, vous transmettez le ContactRepository la classe et le IContactRepository interface avec le AddSingleton extension. C’est l’un des moyens d’implémenter l’injection de dépendances dans ASP.NET Core.

Dans l’écosystème .NET, il existe trois formes principales prises en charge par le framework d’injection de dépendances natif d’ASP.NET Core :

  1. AddSingleton: Cette méthode enregistre une dépendance en tant que singleton. Cela signifie qu’une seule instance du service sera créée et utilisée par l’ensemble de l’application. Exemple:
builder.Services.AddSingleton<IContactRepository, ContactRepository>();
  1. AddScoped: Cette méthode enregistre une dépendance étendue. Il garantit qu’une seule instance du service est créée et utilisée pendant toute la durée de vie d’une requête. Cela signifie que chaque requête reçoit une instance différente de la dépendance. Exemple:
builder.Services.AddScoped<IContactRepository, ContactRepository>();
  1. AddTransient: Cette méthode enregistre une dépendance comme transitoire. Cela signifie qu’une nouvelle instance du service est créée à chaque fois qu’elle est demandée. Exemple:
builder.Services.AddTransient<IContactRepository, ContactRepository>();

Pour rendre l’API fonctionnelle, il vous suffit de créer un point de terminaison pour accéder aux données. Toujours dans le Program.cs fichier, ajoutez le code ci-dessous :

app.MapGet("/contacts", (IContactRepository repository) => {
  var contacts = repository.FindContacts();
  return Results.Ok(contacts);
});

Si vous exécutez la commande dotnet run dans le terminal et accéder à l’adresse http://localhost:PORT dans votre navigateur, vous obtiendrez le résultat suivant :

Accéder aux données

Notez que l’injection de dépendances a fonctionné et que vous pouvez accéder aux données.

Voyons maintenant ce que cela donnerait si vous faisiez la même chose mais sans utiliser l’injection de dépendances. Dans ce cas, le ContactService la classe ressemblerait à ceci :

public class ContactService
{
  private readonly IContactRepository _repository;

  public ContactService()
  {
    _repository = new ContactRepository(); 
  }

  
}

Notez que de cette façon, au lieu de transmettre l’instance du ContactRepository classe dans le constructeur de la classe de service, une nouvelle instance de la ContactRepository la classe est créée manuellement via le new opérateur.

Cette pratique est erronée et ne doit pas être utilisée car elle peut entraîner plusieurs problèmes tels que :

  • Couplage serré: Le ContactClass La classe est étroitement couplée à l’implémentation concrète du référentiel. Cela rend difficile le remplacement de l’implémentation par une autre sans modifier directement le code de la classe. Cela limite la flexibilité et l’extensibilité du système.
  • Difficulté de testabilité : En créant manuellement la dépendance, il devient difficile d’effectuer des tests unitaires efficaces. Les tests peuvent dépendre directement de la mise en œuvre réelle du référentiel, ce qui les rend plus complexes et moins fiables.
  • Entretien complexe : Sans DI, l’ajout ou le remplacement de dépendances nécessite des modifications directes du code source des classes qui les utilisent. Cela augmente le risque d’introduction de bogues et rend la maintenance du code plus complexe et sujette aux problèmes.
  • Réutilisabilité limitée : Sans DI, il n’est pas facile de réutiliser la même instance de référentiel sur plusieurs classes ou composants. Chaque classe devrait créer sa propre instance séparément, ce qui entraînerait une duplication de code inutile.

Quelle est la relation entre DI et DIP ?

Le principe d’inversion de dépendance (DIP) fait référence à l’un des principes de SOLIDE, un ensemble de directives de conception de logiciels qui favorisent la modularité, la flexibilité et la maintenabilité du code. DIP déclare que les classes de haut niveau ne doivent pas dépendre directement des classes de bas niveau. Au lieu de cela, ils doivent s’appuyer sur des abstractions.

Dans le contexte d’ASP.NET Core, l’implémentation DIP est réalisée grâce à l’utilisation d’interfaces ou de classes abstraites pour définir des contrats et des abstractions. Au lieu de classes de haut niveau dépendant directement de classes de bas niveau, elles s’appuient sur des interfaces ou des classes abstraites qui représentent ces dépendances.

ASP.NET Core utilise l’injection de dépendances (DI) pour implémenter DIP. Comme indiqué précédemment, c’est via DI que les dépendances sont injectées dans les classes au moment de l’exécution, plutôt que d’être créées ou instanciées directement dans le code. Cela favorise un couplage lâche entre les classes et facilite le remplacement des implémentations ; vous pouvez facilement fournir différentes implémentations d’une dépendance sans modifier le code qui l’utilise.

En bref, DIP dans ASP.NET Core est obtenu en appliquant le principe d’inversion des dépendances grâce à l’utilisation de DI.

Le modèle de localisateur de services

Lorsque vous parlez de DI dans des langages comme C# et Java, vous rencontrerez le terme Localisateur de services beaucoup.

Localisateur de services est un ancien modèle de conception qui vous permet d’obtenir des instances de services via un localisateur centralisé. Bien qu’il ait été utilisé dans certaines applications et frameworks plus anciens, il présente certains inconvénients par rapport à DI :

  • Couplage serré: Le Service Locator crée un couplage étroit entre les classes qui l’utilisent et le Service Locator, ce qui rend difficile le remplacement et le test de ces dépendances.
  • Difficulté dans la configuration : Service Locator nécessite une configuration explicite pour enregistrer et configurer les services dans le localisateur, ce qui peut être plus laborieux et plus sujet aux erreurs.
  • Manque de transparence: Service Locator ne rend pas explicites les dépendances d’une classe, ce qui rend le code moins compréhensible et plus difficile à maintenir.

Dans ASP.NET Core, Service Locator n’est ni un modèle de conception officiellement pris en charge ni recommandé par le framework. L’approche recommandée pour la résolution des dépendances dans ASP.NET Core est l’injection de dépendances.

Comme nous l’avons vu dans cet article, ASP.NET Core dispose d’un moteur d’injection de dépendances natif robuste qui offre des fonctionnalités avancées telles que le contrôle du cycle de vie, la configuration des services et la prise en charge de l’abstraction des services.

La documentation Microsoft recommande éviter l’utilisation du modèle Service Locator.

En bref, Service Locator peut être utile dans des scénarios spécifiques où il n’est pas possible d’utiliser DI, comme avec du code existant ou une configuration dynamique de services, mais il est toujours préférable d’utiliser l’injection de dépendances.

Conclusion

À première vue, l’injection de dépendances peut sembler un sujet complexe et difficile, mais comme le montre tout au long de l’article, il est possible d’implémenter DI de manière simple, en utilisant uniquement les fonctionnalités natives d’ASP.NET Core.

Chaque développeur travaillant avec des langages orientés objet tels que C# doit comprendre le fonctionnement de DI. Sa mise en œuvre sera courante dans leur routine de travail, en particulier lors de la création de toute application utilisant ASP.NET Core.




Source link

décembre 17, 2023