3 votes

Comment injecter un contrôleur pour MVC4/VS2012/Web API ?

J'ai lu ou essayé de lire beaucoup trop de "comment faire" sur ce sujet et je ne suis arrivé à rien. Unity ? System.Web.Http.Dependencies ? Ninject ? StructureMap ? Ugh. Je veux juste quelque chose de simple qui fonctionne ! Je n'arrive pas à comprendre l'état actuel de la situation. Il y a des approches très différentes et les exemples semblent incomplets. Heck la meilleure piste avait un projet d'exemple avec elle ... que je ne peux pas charger dans VS2010 ou 2012. ARG ! J'ai perdu 3/4 de la journée sur quelque chose qui, à mon avis, aurait dû durer une demi-heure tout au plus et je suis passé à autre chose ! C'est juste de la plomberie !

J'ai un référentiel qui est basé sur des génériques pour traiter un certain nombre de jeux de données qui supportent tous les mêmes opérations.

IRepository

Je veux contrôler à quel référentiel chaque ensemble de données est lié. Cela me permettra de tout lier à un référentiel XML de test, et de les faire passer à un référentiel SQL au fur et à mesure de l'avancement du projet.

J'apprécierais vraiment un peu d'aide pour faire avancer les choses ! Merci.

3voto

RuSs Points 640

On dirait que vous êtes dans l'état où j'étais il y a quelques années.

Note, si vous avez besoin d'aide supplémentaire, je peux vous envoyer du code. C'est juste difficile de mettre tout le code ici.

Je vais essayer d'expliquer l'architecture actuelle du projet sur lequel je travaille. Cet article est un peu long, mais j'essaie de vous donner une vue d'ensemble de la façon dont l'utilisation d'IOC peut vous aider à bien des égards.

J'utilise donc Ninject. Après avoir essayé d'utiliser Castle Windsor pendant un certain temps, j'ai trouvé Ninject facile à mettre en place et à utiliser. Ninject a un site web sympa qui vous aidera à démarrer.

Tout d'abord, la structure de mon projet est la suivante : (de haut en bas et c'est MVC)

Voir - rasoir ViewModel - J'utilise un modèle de vue par vue

ViewModelBuilder - Construit mes modèles de vue pour mes vues (utilisé pour abstraire le code de mon contrôleur afin que ce dernier reste propre et bien rangé).

AutoMapper - pour faire correspondre les entités du domaine à mes modèles de vue

Contrôleur - appelle ma couche de service pour obtenir les entités du domaine

Entités du domaine - représentations de mon domaine

ServiceLayer (couche métier) - Appelle ma couche de référentiel pour obtenir des entités de domaine ou des collections de celles-ci

AutoMapper à nouveau - pour faire correspondre les types personnalisés de mes fournisseurs tiers aux entités de mon domaine

Couche de dépôt - effectue des opérations CRUD sur mes magasins de données

Il s'agit d'une hiérarchie, mais les entités du domaine se situent en quelque sorte à côté et sont utilisées dans quelques couches différentes.

Note : certains outils supplémentaires mentionnés dans ce post sont :

AutoMapper - fait correspondre des entités à d'autres entités - élimine la nécessité d'écrire des tonnes de code de mise en correspondance

Moq - Permet de simuler des éléments pour les tests unitaires. Ceci est mentionné plus tard.

Maintenant, en ce qui concerne Ninject.

Chaque couche est marquée par une interface. Cela doit être fait pour que Ninject puisse se dire .

Lorsque je trouve IVehicleRepository, je l'injecte avec un vrai VehicleRepository ou même avec FakeVehicleRepository si j'ai besoin d'un faux.

(ceci est lié à votre commentaire - "Cela me permettra de tout lier à un référentiel XML de test")

Maintenant chaque couche a un contstructeur pour que Ninject (ou tout autre conteneur IOC) puisse injecter ce dont il a besoin :

public VehicleManager(IVehicleRepository vehicleRepository)
{
    this._vehicleRepository = vehicleRepository;
}

VehicleManager se trouve dans ma serviceLayer (à ne pas confondre avec tout ce qui a trait aux services web). La couche service est en fait ce que nous appellerions la couche métier. Il semble que beaucoup de gens utilisent le mot service. (même si je pense que c'est ennuyeux car cela me fait penser à des services web ou WCF au lieu d'une simple couche métier.... de toute façon...)

Maintenant, sans entrer dans les détails de la configuration de Ninject, la ligne de code suivante dans mon NinjectWebCommon.cs indique à Ninject ce qu'il doit faire :

kernel.Bind<IVehicleRepository>().To<VehicleRepository>().InRequestScope();

Cela dit :

Hey Ninject, quand je demande IVehicleRepository donnez-moi une implémentation concrète de VehicleRepository.

Comme mentionné précédemment, je pourrais remplacer VehicleRepository par FakeVehicleRepository afin de ne pas avoir à lire depuis une vraie base de données.

Ainsi, comme vous pouvez maintenant l'imaginer, chaque couche ne dépend que des interfaces.

Je ne sais pas combien de tests unitaires vous avez fait, mais vous pouvez aussi imaginer que si vous vouliez tester unitairement votre couche de service et qu'elle avait des références concrètes à votre couche de référentiel, vous ne pourriez pas faire de tests unitaires car vous seriez en train de toucher RÉELLEMENT votre référentiel et donc de lire une base de données réelle.

N'oubliez pas que les tests unitaires sont appelés ainsi parce qu'ils ne testent qu'une seule chose. D'où le mot UNITE. Donc, parce que tout ne connaît que les interfaces, cela signifie que vous pouvez tester une méthode sur votre couche service et mocker le référentiel.

Donc si votre couche de service a une méthode comme celle-ci :

public bool ThisIsACar(int id)
{
   bool isCar = false;
   var vehicle = vehicleRepository.GetVehicleById(id);

   if(vehicle.Type == VehicleType.Car)
   {
       isCar = true;
   }
   else
   {
       isCar = false;
   }
}

Vous ne voudriez pas que le VehicleRepository soit appelé pour que vous puissiez Moq ce que le VehicleRepository vous rend. La plupart du temps, vous ne pouvez simuler des choses que si elles implémentent une interface.

So your unit test would look like this (some pseudo code here):
        [TestMethod]
        public void ServiceMethodThisIsACar_Returns_True_When_VehicleIsACar()
        {
            // Arrange
            _mockRepository.Setup(x => x.ThisIsACar(It.IsAny<int>)).returns(new Car with a type of VehicleType.Car)

            // Act
            var vehicleManager = new VehicleManager(_mockVehicleRepository.Object);
            var vehicle = vehicleManager.ThisIsACar(3);

            // Assert
            Assert.IsTrue(vehicle.VehicleType == VehicleType.Car)
        }

Comme vous pouvez le voir, à ce stade, et c'est très simplifié, vous voulez seulement tester l'instruction IF dans votre couche service pour vous assurer que le résultat est correct.

Vous testerez votre référentiel dans ses propres tests unitaires et vous pourrez éventuellement simuler la structure de l'entité si vous l'utilisez.

Donc, dans l'ensemble, je dirais qu'il faut utiliser le conteneur IOC qui vous permet d'être opérationnel le plus rapidement et avec le moins de douleur possible.

Je dirais aussi, essayez de tester en unité tout ce que vous pouvez. C'est génial pour plusieurs raisons différentes. Évidemment, cela permet de tester le code que vous avez écrit, mais cela vous montrera aussi immédiatement si vous avez fait quelque chose de stupide, comme créer un dépôt concret. Vous verrez rapidement que vous n'avez pas d'interface à simuler dans vos tests unitaires et cela vous amènera à revenir en arrière et à remanier votre code.

J'ai découvert qu'avec le CIO, il faut un certain temps pour comprendre. Ça vous perturbe jusqu'à ce qu'un jour vous ayez le déclic. Après cela, c'est tellement facile que vous vous demandez comment vous avez pu vivre sans.

Voici une liste de choses dont je ne peux pas me passer Automapper Moq Fluent Validation Resharper - certains le détestent, moi je l'adore, surtout pour son interface utilisateur de test unitaire.

Bref, ça devient trop long. Dites-moi ce que vous en pensez.

Merci RuSs

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X