Partager via


Injection de dépendances dans API Web ASP.NET 2

Télécharger le projet terminé

Ce tutoriel montre comment injecter des dépendances dans votre contrôleur de API Web ASP.NET.

Versions logicielles utilisées dans le didacticiel

Qu’est-ce que l’injection de dépendances ?

Une dépendance est un objet qui nécessite un autre objet. Par exemple, il est courant de définir un référentiel qui gère l’accès aux données. Nous allons illustrer un exemple. Tout d’abord, nous allons définir un modèle de domaine :

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Voici une classe de référentiel simple qui stocke des éléments dans une base de données, à l’aide d’Entity Framework.

public class ProductsContext : DbContext
{
    public ProductsContext()
        : base("name=ProductsContext")
    {
    }
    public DbSet<Product> Products { get; set; }
}

public class ProductRepository : IDisposable
{
    private ProductsContext db = new ProductsContext();

    public IEnumerable<Product> GetAll()
    {
        return db.Products;
    }
    public Product GetByID(int id)
    {
        return db.Products.FirstOrDefault(p => p.Id == id);
    }
    public void Add(Product product)
    {
        db.Products.Add(product);
        db.SaveChanges();
    }

    protected void Dispose(bool disposing)
    {
        if (disposing)
        {
            if (db != null)
            {
                db.Dispose();
                db = null;
            }
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

Nous allons maintenant définir un contrôleur d’API web qui prend en charge les requêtes GET pour Product les entités. (Je quitte POST et d’autres méthodes par souci de simplicité.) Voici une première tentative :

public class ProductsController : ApiController
{
    // This line of code is a problem!
    ProductRepository _repository = new ProductRepository();

    public IEnumerable<Product> Get()
    {
        return _repository.GetAll();
    }

    public IHttpActionResult Get(int id)
    {
        var product = _repository.GetByID(id);
        if (product == null)
        {
            return NotFound();
        }
        return Ok(product);
    }
}

Notez que la classe de contrôleur dépend ProductRepositoryde , et nous laissons le contrôleur créer l’instance ProductRepository . Toutefois, il est difficile de coder en dur la dépendance de cette façon, pour plusieurs raisons.

  • Si vous souhaitez remplacer ProductRepository par une autre implémentation, vous devez également modifier la classe de contrôleur.
  • Si les ProductRepository dépendances sont présentes, vous devez les configurer à l’intérieur du contrôleur. Pour un projet volumineux avec plusieurs contrôleurs, votre code de configuration devient dispersé dans votre projet.
  • Il est difficile d’effectuer un test unitaire, car le contrôleur est codé en dur pour interroger la base de données. Pour un test unitaire, vous devez utiliser un référentiel fictif ou stub, qui n’est pas possible avec la conception actuelle.

Nous pouvons résoudre ces problèmes en injectant le référentiel dans le contrôleur. Tout d’abord, refactorisez la ProductRepository classe dans une interface :

public interface IProductRepository
{
    IEnumerable<Product> GetAll();
    Product GetById(int id);
    void Add(Product product);
}

public class ProductRepository : IProductRepository
{
    // Implementation not shown.
}

Fournissez ensuite le IProductRepository paramètre de constructeur :

public class ProductsController : ApiController
{
    private IProductRepository _repository;

    public ProductsController(IProductRepository repository)  
    {
        _repository = repository;
    }

    // Other controller methods not shown.
}

Cet exemple utilise l’injection de constructeur. Vous pouvez également utiliser l’injection setter, où vous définissez la dépendance par le biais d’une méthode ou d’une propriété setter.

Toutefois, il existe maintenant un problème, car votre application ne crée pas directement le contrôleur. L’API web crée le contrôleur lorsqu’il achemine la requête, et l’API web ne connaît rien.IProductRepository C’est là que le programme de résolution des dépendances de l’API web est disponible.

Programme de résolution des dépendances de l’API web

L’API web définit l’interface IDependencyResolver pour la résolution des dépendances. Voici la définition de l’interface :

public interface IDependencyResolver : IDependencyScope, IDisposable
{
    IDependencyScope BeginScope();
}

public interface IDependencyScope : IDisposable
{
    object GetService(Type serviceType);
    IEnumerable<object> GetServices(Type serviceType);
}

L’interface IDependencyScope a deux méthodes :

  • GetService crée une instance d’un type.
  • GetServices crée une collection d’objets d’un type spécifié.

La méthode IDependencyResolver hérite d’IDependencyScope et ajoute la méthode BeginScope. Je vais parler des étendues plus loin dans ce tutoriel.

Lorsque l’API Web crée une instance de contrôleur, elle appelle d’abord IDependencyResolver.GetService, en passant le type de contrôleur. Vous pouvez utiliser ce hook d’extensibilité pour créer le contrôleur, en résolvant les dépendances. Si GetService retourne null, l’API web recherche un constructeur sans paramètre sur la classe de contrôleur.

Résolution des dépendances avec le conteneur Unity

Bien que vous puissiez écrire une implémentation IDependencyResolver complète à partir de zéro, l’interface est vraiment conçue pour agir comme un pont entre l’API Web et les conteneurs IoC existants.

Un conteneur IoC est un composant logiciel responsable de la gestion des dépendances. Vous inscrivez des types auprès du conteneur, puis utilisez le conteneur pour créer des objets. Le conteneur détermine automatiquement les relations de dépendance. De nombreux conteneurs IoC vous permettent également de contrôler des éléments tels que la durée de vie et l’étendue des objets.

Remarque

« IoC » signifie « inversion du contrôle », qui est un modèle général dans lequel une infrastructure appelle du code d’application. Un conteneur IoC construit vos objets pour vous, ce qui « inverse » le flux de contrôle habituel.

Pour ce tutoriel, nous allons utiliser Unity à partir de Microsoft Patterns &Practices. (D’autres bibliothèques populaires incluent Castle Windsor, Spring.Net, Autofac, Ninject, Simple Injector et StructureMap.) Vous pouvez utiliser nuGet Gestionnaire de package pour installer Unity. Dans le menu Outils de Visual Studio, sélectionnez NuGet Gestionnaire de package, puis Gestionnaire de package Console. Dans la fenêtre Gestionnaire de package console, tapez la commande suivante :

Install-Package Unity

Voici une implémentation d’IDependencyResolver qui encapsule un conteneur Unity.

using Microsoft.Practices.Unity;
using System;
using System.Collections.Generic;
using System.Web.Http.Dependencies;

public class UnityResolver : IDependencyResolver
{
    protected IUnityContainer container;

    public UnityResolver(IUnityContainer container)
    {
        if (container == null)
        {
            throw new ArgumentNullException(nameof(container));
        }
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        try
        {
            return container.Resolve(serviceType);
        }
        catch (ResolutionFailedException exception)
        {
            throw new InvalidOperationException(
                $"Unable to resolve service for type {serviceType}.",
                exception)
        }
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        try
        {
            return container.ResolveAll(serviceType);
        }
        catch (ResolutionFailedException exception)
        {
            throw new InvalidOperationException(
                $"Unable to resolve service for type {serviceType}.",
                exception)
        }
    }

    public IDependencyScope BeginScope()
    {
        var child = container.CreateChildContainer();
        return new UnityResolver(child);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        container.Dispose();
    }
}

Configuration du programme de résolution des dépendances

Définissez le programme de résolution de dépendances sur la propriété DependencyResolver de l’objet HttpConfiguration global.

Le code suivant inscrit l’interface IProductRepository avec Unity, puis crée un UnityResolver.

public static void Register(HttpConfiguration config)
{
    var container = new UnityContainer();
    container.RegisterType<IProductRepository, ProductRepository>(new HierarchicalLifetimeManager());
    config.DependencyResolver = new UnityResolver(container);

    // Other Web API configuration not shown.
}

Durée de vie des dépendances et du contrôleur

Les contrôleurs sont créés par requête. Pour gérer les durées de vie des objets, IDependencyResolver utilise le concept d’une étendue.

Le programme de résolution de dépendance attaché à l’objet HttpConfiguration a une étendue globale. Lorsque l’API web crée un contrôleur, elle appelle BeginScope. Cette méthode retourne un IDependencyScope qui représente une étendue enfant.

L’API web appelle ensuite GetService sur l’étendue enfant pour créer le contrôleur. Une fois la requête terminée, l’API web appelle Dispose sur l’étendue enfant. Utilisez la méthode Dispose pour supprimer les dépendances du contrôleur.

La façon dont vous implémentez BeginScope dépend du conteneur IoC. Pour Unity, l’étendue correspond à un conteneur enfant :

public IDependencyScope BeginScope()
{
    var child = container.CreateChildContainer();
    return new UnityResolver(child);
}

La plupart des conteneurs IoC ont des équivalents similaires.