Mise en œuvre de l'injection de dépendances avec Autofac dans une application ASP.NET MVC

L'intégration de l'injection de dépendances (DI) est une pratique fondamentale pour construire des applications .NET modulaires, testables et maintenables. Dans le contexte d'une application ASP.NET MVC, un conteneur d'Inversion de Contrôle (IoC) comme Autofac simplifie considérablement la gestion du cycle de vie des objets et la résoltuion des dépendances entre les composants, tels que les contrôleurs, les services métier et les couches d'accès aux données. Ce guide détaillé vous montrera comment configurer et utiliser Autofac pour une gestion efficace des dépendances dans votre projet ASP.NET MVC.

1. Installation des packages NuGet

Pour commencer, vous devez ajouter les bibliothèques Autofac et son intégration pour ASP.NET MVC à votre projet. Utilisez la console du Gestionnaire de packages NuGet :

Install-Package Autofac
Install-Package Autofac.Mvc5

2. Configuration globale du conteneur IoC (Global.asax.cs)

La configuration du conteneur Autofac s'effectue au démarrage de l'application, dans la méthode Application_Start de votre fichier Global.asax.cs. C'est ici que nous allons enregistrer les contrôleurs MVC, ainsi que toutes les interfaces et leurs implémentations concrètes.

using Autofac;
using Autofac.Integration.Mvc;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace MonApplicationMVC
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            // Configurations MVC standard
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            // Initialisation d'Autofac
            var containerBuilder = new ContainerBuilder();

            // Enregistrement de tous les contrôleurs MVC dans l'assemblage courant.
            // Autofac analysera les constructeurs des contrôleurs pour injecter les dépendances.
            containerBuilder.RegisterControllers(Assembly.GetExecutingAssembly());

            // Appel de la méthode pour définir les règles d'enregistrement spécifiques de vos composants.
            ConfigureApplicationServices(containerBuilder);

            // Construction du conteneur de dépendances final.
            var dependencyContainer = containerBuilder.Build();

            // Définition d'Autofac comme résolveur de dépendances par défaut pour ASP.NET MVC.
            DependencyResolver.SetResolver(new AutofacDependencyResolver(dependencyContainer));
        }

        private static void ConfigureApplicationServices(ContainerBuilder builder)
        {
            // Enregistrement par convention : tous les types non abstraits dont le nom se termine par "Service"
            // sont enregistrés et exposés via toutes les interfaces qu'ils implémentent.
            // Utilisation de InstancePerRequest() pour assurer un cycle de vie par requête HTTP.
            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
                   .Where(t => t.Name.EndsWith("Service") && !t.IsAbstract)
                   .AsImplementedInterfaces()
                   .InstancePerRequest();

            // Enregistrement d'un service CRUD générique.
            // 'ServiceCRUDGenerique<>' sera résolu pour l'interface 'IGenericCrudService<>'.
            builder.RegisterGeneric(typeof(ServiceCRUDGenerique<>))
                   .As(typeof(IGenericCrudService<>))
                   .InstancePerRequest();
        }
    }
}

3. Injection des services dans les contrôleurs

Avec Autofac configuré, l'injection de dépendances dans vos contrôleurs devient simple. Il suffit de déclarer les interfaces de vos services dans le constructeur du contrôleur, et Autofac injectera automatiquement les implémentations concrètes.

using System.Web.Mvc;
// ... autres directives using

namespace MonApplicationMVC.Controllers
{
    public class EntreeEnregistrementController : Controller
    {
        private readonly IGenericCrudService<TypeEnregistrement> _enregistrementService;

        // Le constructeur du contrôleur reçoit automatiquement l'instance de IGenericCrudService.
        public EntreeEnregistrementController(IGenericCrudService<TypeEnregistrement> serviceEnregistrement)
        {
            _enregistrementService = serviceEnregistrement;
        }

        // Exemple d'action utilisant le service injecté
        public ActionResult Index()
        {
            // Utilisez _enregistrementService pour effectuer des opérations CRUD
            // var allEntries = _enregistrementService.GetList(someParentId);
            return View();
        }
    }
}

4. Gestion de la connexion à la base de données avec SqlSugar

Pour l'accès à la base de données, il est recommandé d'utiliser une abstraction pour le client de base de données (ici, SqlSugarClient). Cela permet une meilleure séparation des préoccupations et facilite le remplacement du fournisseur de données si nécessaire.

Interface du fournisseur de base de données :

Cette interface définit un contrat pour obtenir une instance du client de base de données, tout en gérant sa libération des ressources.

using System;

namespace MonApplicationMVC.Infrastructure.DataAccess
{
    /// <summary>
    /// Fournit une instance du client de base de données et gère sa libération.
    /// </summary>
    public interface IDatabaseClientProvider<TClient> : IDisposable
    {
        TClient GetClientInstance();
    }
}

Implémentation pour SqlSugar :

Cette classe concrète implémente l'interface IDatabaseClientProvider pour SqlSugarClient, configurant la connexion à partir de Web.config.

using SqlSugar;
using System.Configuration; // Nécessaire pour accéder aux chaînes de connexion

namespace MonApplicationMVC.Infrastructure.DataAccess
{
    /// <summary>
    /// Implémentation de IDatabaseClientProvider pour SqlSugarClient.
    /// </summary&
    public class SqlSugarClientProvider : IDatabaseClientProvider<SqlSugarClient>
    {
        private SqlSugarClient _sqlSugarClient;
        private bool _isDisposed = false;

        public SqlSugarClientProvider()
        {
            string connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
            
            _sqlSugarClient = new SqlSugarClient(new ConnectionConfig()
            {
                ConnectionString = connectionString,
                DbType = DbType.SqlServer, // Assurez-vous d'utiliser le bon type de base de données (e.g., SqlServer, MySql, Oracle, PostgreSql)
                IsAutoCloseConnection = true, // Important pour le cycle de vie des applications web
                InitKeyType = InitKeyType.Attribute // Pour la détection des clés primaires via attributs
            });
        }

        public SqlSugarClient GetClientInstance()
        {
            return _sqlSugarClient;
        }

        public void Dispose()
        {
            Dispose(true);
            // Indique que le finaliseur n'a pas besoin de s'exécuter
            System.GC.SuppressFinalize(this); 
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                if (disposing)
                {
                    // Libère les ressources gérées
                    _sqlSugarClient?.Dispose(); 
                }
                // Libère les ressources non gérées (non applicable ici car SqlSugarClient gère ses propres ressources)
                _isDisposed = true;
            }
        }
    }
}

Enregistrement du fournisseur dans Autofac :

Ajoutez l'enregistrement de ce fournisseur dans la méthode ConfigureApplicationServices de Global.asax.cs.

private static void ConfigureApplicationServices(ContainerBuilder builder)
{
    // ... autres enregistrements

    // Enregistrement du fournisseur SqlSugarClient.
    // Une instance unique est fournie par requête HTTP.
    builder.RegisterType<SqlSugarClientProvider>()
           .As<IDatabaseClientProvider&lt<sqlsugarclient .instanceperrequest=""></sqlsugarclient>

Utilisation du client de base de données injecté :

Vous pouvez maintenant injecter IDatabaseClientProvider<SqlSugarClient> dans n'importe quel service qui a besoin d'accéder à la base de données.

using SqlSugar;
// ... autres directives using

namespace MonApplicationMVC.Services
{
    public class MonServiceDonnees
    {
        private readonly SqlSugarClient _dbClient;

        public MonServiceDonnees(IDatabaseClientProvider<SqlSugarClient> dbProvider)
        {
            _dbClient = dbProvider.GetClientInstance();
        }

        // Exemple de méthode utilisant le client SqlSugar
        public List<MonEntite> ObtenirToutesLesEntites()
        {
            return _dbClient.Queryable<MonEntite>().ToList();
        }
    }
}

5. Services CRUD génériques

Pour standardiser les opérations de base (Création, Lecture, Mise à jour, Suppression), vous pouvez implémenter un service CRUD générique qui opère sur n'importe quel type d'entité, utilisant le client de base de données injecté.

Interface du service CRUD générique :

using System;
using System.Collections.Generic;
using System.Web.Mvc; // Retourne ActionResult pour une intégration directe dans les contrôleurs

namespace MonApplicationMVC.Services.Generic
{
    /// <summary>
    /// Interface pour un service CRUD générique.
    /// </summary>
    /// <typeparam name="TEntity">Le type d'entité sur lequel le service opère.</typeparam>
    public interface IGenericCrudService<TEntity> where TEntity : class
    {
        ActionResult Create(TEntity entity);
        ActionResult Delete(Guid entityId);
        ActionResult Update(TEntity entity);
        ActionResult GetList(Guid parentId); // Exemple de méthode de recherche avec un ID parent
    }
}

Implémentation du service CRUD générique :

Cette implémentation utilise le SqlSugarClient injecté pour interagir avec la base de données. Pour la simplicité et afin de réduire la similitude, les réponses sont directement des JsonResult. Les vérifications de validation et la logique de réponse peuvent être externalisées dans des helpers si désiré.

using MonApplicationMVC.Infrastructure.DataAccess;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Mvc;

namespace MonApplicationMVC.Services.Generic
{
    /// <summary>
    /// Implémentation générique d'un service CRUD utilisant SqlSugar.
    /// </summary>
    /// <typeparam name="TEntity">Type de l'entité, doit être une classe et avoir un constructeur sans paramètres.</typeparam>
    public class ServiceCRUDGenerique<TEntity> : IGenericCrudService<TEntity>
        where TEntity : class, new()
    {
        private readonly SqlSugarClient _sqlSugarDb;

        public ServiceCRUDGenerique(IDatabaseClientProvider<SqlSugarClient> dbProvider)
        {
            _sqlSugarDb = dbProvider.GetClientInstance();
        }

        // Méthode utilitaire pour créer des réponses JSON standardisées.
        private JsonResult CreateJsonResult(bool success, string message, object data = null)
        {
            return new JsonResult
            {
                Data = new { success, message, data },
                JsonRequestBehavior = JsonRequestBehavior.AllowGet // Permet la réponse JSON pour les requêtes GET
            };
        }

        public ActionResult Create(TEntity entity)
        {
            try
            {
                // Tente d'assigner un GUID si la propriété "VGUID" existe et est vide.
                PropertyInfo vguidProperty = typeof(TEntity).GetProperty("VGUID");
                if (vguidProperty != null && vguidProperty.PropertyType == typeof(Guid) && (Guid)vguidProperty.GetValue(entity) == Guid.Empty)
                {
                    vguidProperty.SetValue(entity, Guid.NewGuid());
                }

                // Ici, des validations métier plus complexes pourraient être effectuées.
                // Ex: if (!ValidationService.Validate(entity)) return CreateJsonResult(false, "Données invalides.");

                var rowsAffected = _sqlSugarDb.Insertable(entity).ExecuteCommand();

                if (rowsAffected > 0)
                {
                    return CreateJsonResult(true, "L'enregistrement a été créé avec succès.");
                }
                else
                {
                    return CreateJsonResult(false, "Échec de la création de l'enregistrement.");
                }
            }
            catch (Exception ex)
            {
                return CreateJsonResult(false, $"Erreur lors de la création de l'enregistrement : {ex.Message}");
            }
        }

        public ActionResult Delete(Guid entityId)
        {
            try
            {
                // Supprime l'entité par son ID. Assumons que l'ID est la clé primaire.
                var rowsAffected = _sqlSugarDb.Deleteable<TEntity>().Where(entityId).ExecuteCommand();

                if (rowsAffected > 0)
                {
                    return CreateJsonResult(true, "L'enregistrement a été supprimé avec succès.");
                }
                else
                {
                    return CreateJsonResult(false, "Échec de la suppression : ID introuvable ou erreur interne.");
                }
            }
            catch (Exception ex)
            {
                return CreateJsonResult(false, $"Erreur lors de la suppression de l'enregistrement : {ex.Message}");
            }
        }

        public ActionResult Update(TEntity entity)
        {
            try
            {
                // Récupère la valeur de la propriété "VGUID" pour identifier l'entité à mettre à jour.
                PropertyInfo vguidProperty = typeof(TEntity).GetProperty("VGUID");
                if (vguidProperty == null || vguidProperty.PropertyType != typeof(Guid))
                {
                    return CreateJsonResult(false, "La propriété 'VGUID' est requise pour la mise à jour.");
                }
                Guid entityGuid = (Guid)vguidProperty.GetValue(entity);

                var rowsAffected = _sqlSugarDb.Updateable(entity).Where(entityGuid).ExecuteCommand();

                if (rowsAffected > 0)
                {
                    return CreateJsonResult(true, "L'enregistrement a été mis à jour avec succès.");
                }
                else
                {
                    return CreateJsonResult(false, "Échec de la mise à jour : ID introuvable ou aucune modification n'a été effectuée.");
                }
            }
            catch (Exception ex)
            {
                return CreateJsonResult(false, $"Erreur lors de la mise à jour de l'enregistrement : {ex.Message}");
            }
        }

        public ActionResult GetList(Guid parentId)
        {
            try
            {
                // Récupère une liste d'entités basée sur un ID parent (par exemple, "EntryRegistrationID").
                // Cette requête utilise Ado.SqlQuery pour une flexibilité maximale, similaire à la requête SQL originale.
                var items = _sqlSugarDb.Ado.SqlQuery<TEntity>($"SELECT * FROM {typeof(TEntity).Name} WHERE EntryRegistrationID = @parentId",
                                                           new { parentId = parentId })
                                      .ToList();
                return CreateJsonResult(true, "Liste des enregistrements récupérée avec succès.", items);
            }
            catch (Exception ex)
            {
                return CreateJsonResult(false, $"Erreur lors de la récupération de la liste : {ex.Message}");
            }
        }
    }
}

Étiquettes: AutoFac ASP.NET MVC Dependency Injection Inversion of Control SqlSugar

Publié le 22 juillet à 11h52