Article original : Create a REST API with .NET 5 and C#
.NET est un framework logiciel populaire développé par Microsoft.
Nous venons de publier un cours sur la chaîne YouTube freeCodeCamp.org qui vous apprendra à créer une API REST de bout en bout à partir de zéro en utilisant les dernières innovations de .NET 5 et Visual Studio Code. Le cours utilise le langage de programmation C#.
Julio Casal a développé ce cours. Il est ingénieur logiciel principal chez Microsoft et il est également un excellent enseignant.
Voici toutes les sections couvertes dans ce cours complet :
Installation
- Introduction
- Création du projet
- Exploration des fichiers de projet générés
- Confiance dans le certificat auto-signé
- Exploration de la page d'interface utilisateur Swagger par défaut
- Configuration des paramètres de Visual Studio Code
Entité, Dépôt, Contrôleur GET
- Introduction
- Ajout d'une entité
- Ajout d'un dépôt en mémoire
- Création du contrôleur
- Implémentation de GET tous les éléments
- Implémentation de GET un seul élément
- Retour d'un code d'état 404 NotFound
Injection de dépendances, DTOs
- Introduction
- Qu'est-ce que l'injection de dépendances ?
- Extraction de l'interface du dépôt
- Injection du dépôt dans le contrôleur
- Enregistrement du dépôt en tant que singleton
- Ajout d'un objet de transfert de données DTO
- Création de la méthode d'extension AsDto
POST, PUT, DELETE
- Introduction
- Implémentation de POST
- Ajout de validations via des annotations de données
- Implémentation de PUT
- Implémentation de DELETE
Persistance des entités avec MongoDB
- Introduction
- Utilisation de Postman
- Création d'un dépôt MongoDB
- Utilisation du package NuGet MongoDB.Driver
- Implémentation de MongoDB Create
- Exécution du conteneur Docker MongoDB
- Configuration des paramètres de connexion MongoDB
- Enregistrement du singleton MongoClient
- Test de l'intégration MongoDB
- Exploration de la base de données créée dans VS Code
- Implémentation de MongoDB Get, Update et Delete
Tâches, Async et Await
- Introduction
- Utilisation du suffixe Async
- Utilisation des tâches dans le dépôt
- Utilisation de async et await
- Retour des tâches terminées
- Utilisation des tâches dans le contrôleur
- Test des méthodes async dans Postman
Secrets et Vérifications de santé
- Introduction
- Activation de l'authentification dans MongoDB
- Utilisation du gestionnaire de secrets .NET
- Utilisation des informations d'identification MongoDB dans le service
- Introduction aux vérifications de santé
- Ajout d'un point de terminaison pour les vérifications de santé
- Ajout d'une vérification de santé MongoDB
- Ajout de vérifications pour la préparation et la disponibilité
- Personnalisation de la réponse de vérification de santé
- Exploration d'autres packages NuGet de vérification de santé
Docker
- Introduction
- Qu'est-ce que Docker ?
- Suppression de la redirection https
- Génération d'un Dockerfile dans VS Code
- Construction de l'image Docker
- Ajout d'un réseau Docker
- Exécution des conteneurs dans le réseau Docker
- Exécution de l'API REST dans Docker
- Poussage de l'image du conteneur vers Docker Hub
- Exploration de l'image dans Docker Hub
- Récupération de l'image vers la machine locale
Kubernetes
- Introduction
- Qu'est-ce que Kubernetes ?
- Activation d'un cluster Kubernetes dans Docker Desktop
- Installation de l'extension Kubernetes pour VS Code
- Déclaration du déploiement Kubernetes de l'API REST
- Création d'un secret dans Kubernetes
- Déclaration des sondes de santé
- Déclaration du service Kubernetes de l'API REST
- Création des ressources de l'API REST dans Kubernetes
- Déclaration du StatefulSet Kubernetes de MongoDB
- Déclaration du service Kubernetes de MongoDB
- Création des ressources MongoDB dans Kubernetes
- Test de l'API REST hébergée dans Kubernetes
- Exploration de la capacité d'auto-guérison de Kubernetes
- Mise à l'échelle des pods Kubernetes
- Ajout de logs via ILogger
- Obtention d'une nouvelle version d'image dans Kubernetes
- Équilibrage de charge des requêtes entre les pods
Tests unitaires et TDD
- Introduction
- Qu'est-ce que les tests unitaires ?
- Qu'est-ce que le développement piloté par les tests ?
- Restructuration des fichiers et répertoires
- Création du projet de test xUnit
- Construction de plusieurs projets dans VS Code
- Ajout de packages NuGet pour les tests unitaires
- Test de GetItemAsync pour un élément inexistant
- Utilisation du modèle AAA
- Simulation des dépendances via Moq
- Exécution des tests dans VS Code
- Utilisation de l'extension .NET Core Test Explorer
- Test de GetItemAsync pour un élément existant
- Utilisation de FluentAssertions
- Test de GetItemsAsync
- Test de CreateItemAsync
- Test de UpdateItemAsync
- Test de DeleteItemAsync
- Refactorisation et détection des régressions
- Utilisation du TDD pour tester une méthode non encore créée
- Retour au vert en corrigeant le test échoué
- Test de la nouvelle méthode du contrôleur dans Postman
Regardez le cours ci-dessous ou sur la chaîne YouTube freeCodeCamp.org (6 heures de visionnage).
Transcription de la vidéo
(auto-traduit)
Vous allez apprendre à utiliser .NET 5 pour créer une API Rest. Le créateur de ce cours est un ingénieur logiciel principal chez Microsoft et il est également un excellent enseignant.
L'API REST permet à votre application ou système d'exposer ses fonctionnalités à plusieurs types de clients, à la fois à l'intérieur et à l'extérieur de votre réseau, y compris des clients à travers Internet. C'est ce que vous utiliseriez si vous vouliez écrire un programme pour collecter des données à partir de Twitter, Yahoo Finance, ou même la NASA. Si vous envisagez de construire votre propre API REST et que vous envisagez la plateforme dotnet pour cela, restez avec moi, car je vais vous montrer comment faire cela de bout en bout en utilisant les dernières innovations fournies par dotnet. Five, j'espère que vous allez apprécier.
Dans la première partie de ce tutoriel, vous allez apprendre le scénario à utiliser dans le tutoriel, comment créer un projet Web API dotnet five à partir de zéro, comment utiliser Visual Studio code pour construire et déboguer le projet, nous allons travailler sur la façon de dessiner le certificat de développement installé par dotnet. Five, et cela sera nécessaire pour l'accès HTTPS et comment utiliser swagger UI pour interagir avec l'API.
Pour suivre le tutoriel, vous aurez besoin de quelques éléments, notamment le SDK dotnet five, Visual Studio code et une compréhension de base du langage C sharp.
Maintenant, parlons de ce scénario que nous allons utiliser comme domaine pour ce tutoriel. Imaginons que nous avons une sorte de système de catalogue. Et nous avons un tas d'articles disponibles. Dans ce sens, comme je suis un joueur vidéo, j'aime penser à ces articles comme des objets que j'utiliserai dans un jeu vidéo. Des objets comme des potions, des épées, des boucliers et des choses comme ça. D'accord. Donc c'est un système que nous avons en place, il a un catalogue. Et bien sûr, nous allons avoir des utilisateurs qui voudront gérer ce catalogue via leur navigateur, n'est-ce pas ? Ils ont un navigateur, ils veulent gérer ces éléments de catalogue, d'une manière ou d'une autre.
Donc ce qu'ils voudraient faire, c'est bien, comment allons-nous créer des éléments dans le catalogue ? Comment pouvons-nous récupérer la liste des éléments actuellement disponibles dans ce catalogue ? Comment pouvons-nous mettre à jour les propriétés des éléments. Et comment pouvons-nous supprimer des éléments dans ce catalogue. Comme c'est le cas aujourd'hui, nous avons le catalogue disponible. Mais nous manquons, nous n'avons pas de moyen d'exposer ce catalogue à l'internet afin que les gens puissent aller de l'avant et le gérer à partir du navigateur. C'est là que nous allons introduire notre API REST pour ce catalogue. Et au cours de ce tutoriel, nous allons voir comment construire cette API REST à partir de zéro en utilisant dotnet. Five.
Donc nous voici dans Visual Studio Code. Et la première chose que nous allons faire est d'ouvrir un tout nouveau terminal. Et dans ce terminal, nous allons simplement passer au répertoire où nous allons créer notre projet et créer un projet, nous allons utiliser le CLI dotnet.
Pour ce faire, nous allons simplement dire que, que new et le type de produit que nous voulons créer pour notre API REST, ce sera Web API. Et le nom du projet sera catalogue, appuyez sur Entrée.
Et cela crée tous les fichiers de votre projet, basé sur le modèle Web API. Donc maintenant je vais ouvrir ce dossier qui a été généré, catalogue.
Et, comme d'habitude, lorsque vous ouvrez un projet dotnet dans Visual Studio Code, il vous demandera d'ajouter quelques fichiers supplémentaires pour construire et déboguer le projet. Donc je vais dire Oui, et ces fichiers sont générés sous .vscode. Donc sur le côté gauche, vous pouvez voir tous les fichiers générés. Et prenons un rapide aperçu de chacun de ces fichiers
comme un rapide aperçu. Donc la première partie que nous allons regarder est le fichier .csproj. Ce fichier est utilisé, c'est ce qu'on appelle le fichier de projet. Et il est utilisé pour déclarer comment nous allons construire ce projet. Dans ce cas, nous disons que nous allons utiliser le SDK web dotnet pour construire le projet, qui inclut un tas d'outils et de tâches spécifiquement conçus pour les projets web. La chose intéressante suivante sera le framework cible, un moniteur ou framework cible, qui dans notre cas est net five. Le framework cible définit les API ou les API ou quel type d'API seront disponibles pour votre projet. Dans ce cas, net five est parfaitement adapté pour nous. Et la chose suivante sera un tas de packages NuGet que nous n'allons pas approfondir pour l'instant. Mais ce sont simplement des dépendances que nous avons déjà acquises pour ce projet. Fermons cela et ensuite nous allons jeter un coup d'œil à ce programme. C
Sa prononciation est ce que nous appelons le point d'entrée de l'application. Et ce qu'il fera, c'est simplement démarrer le host. Je veux dire, le processus va devenir l'hôte de notre problème. Et il déclare également un tas de valeurs par défaut. Et il configure également ce que nous appelons la classe de démarrage pour notre projet. Donc allons-y, allons à startup et voyons ce qui se passe là.
Vraiment, les principales choses dans startup sont que
nous avons cette propriété appelée configuration que nous recevons dans le constructeur de startup. Cela peut être utilisé chaque fois que vous devez lire certaines informations de configuration à partir de plusieurs sources, comme des variables d'environnement ou des fichiers, différents types de fichiers, ou un tas d'autres endroits, une configuration que vous ne voulez pas coder en dur dans votre service. La méthode intéressante suivante est configure services. Et c'est l'endroit où vous
enregistrerez tous les services que vous allez utiliser dans votre application. Et nous en parlerons plus tard dans cette vidéo. Et la dernière pièce intéressante est la méthode Configure.
C'est là que nous configurons ce que nous appelons le
pipeline, le pipeline de requête sur ASP. net. Et cela définit simplement un tas de middlewares typiques, qui sont
des composants supplémentaires qui s'exécuteront avant votre contrôleur ou votre code.
Chacun de ces éléments peut s'exécuter
lorsqu'une requête arrive dans le processus ASP. NET. Et de là, jusqu'à ce que votre code s'exécute. Mais nous n'allons pas explorer cette partie dans cette vidéo.
Quelques autres choses sont donc nous avons une prévision météorologique. Donc c'est un modèle qui est généré pour cette application d'exemple ici, il n'y a que quelques propriétés très simples ici. Et avec ce modèle, il y a un contrôleur. Maintenant, le contrôleur dans ASP. NET est simplement la classe qui gère la route.
Oui, assez beaucoup les routes que votre service expose, mais nous n'allons pas les utiliser dans cette vidéo. Donc ne nous attardons pas trop là-dessus.
Quelques autres fichiers, appsettings.json, c'est là que vous pouvez déclarer la configuration, qui va être que vous ne voulez pas utiliser de code dur dans votre programme, dans votre code source. Et pour l'instant, il n'a que quelques configurations pour la journalisation et les hôtes autorisés dans l'application. Il existe également une variante de appsettings JSON, qui est appsettings.Development.json. Donc développement, je veux dire, le fait qu'il dit que cela dit appsettings.Development.json signifie que nous exécutons dans un environnement de développement,
ces paramètres prendront le pas sur le appsettings.json. Donc vous pourriez avoir un tas de ces fichiers appsettings pour chacun de vos environnements comme la production, le test, l'intégration, tous ces environnements.
Et je parlerai des environnements, peut-être un bon moment pour jeter un coup d'œil aux fichiers .vscode, qui sont task.json et launch.json. Donc task.json est simplement un fichier qui déclare les tâches avec ces concepts clés et pièces de votre code, vous pouvez déclarer des tâches et une tâche peut être un tas de choses. Et dans notre cas, la chose la plus intéressante est le fait que nous voulons utiliser la commande dotnet build. Donc dotnet build, qui sera utilisée pour construire notre code. Et en termes de launch.json, ce fichier contrôle ce qui sera lancé ou exécuté lorsque nous faisons un F5. Ou lorsque nous commençons à déboguer le code, dans ce cas, il pointe déjà vers la bonne DLL pour commencer à déboguer.
Enfin,
nous avons également des paramètres de lancement. Donc les fichiers launchSettings.json sont vraiment la seule partie intéressante ici qu'ils veulent regarder, c'est l'URL de l'application. Ici, nous définissons les URLs, URLs impériales, pour notre application, dans ce cas, nous disons que nous allons servir notre serveur en localhost 5004, la version HTTPS sera servie dans laquelle sera la version par défaut sera 5001. Nous déclarons également la variable d'environnement ASPNETCORE_ENVIRONMENT. Et nous la définissons comme développement. Et donc
aussi, dans Visual Studio Code. Ce n'est pas celui qui a été codé en dur, celui qui sera codé en dur est celui qui va être mangé à midi, launchSettings.json. Comme vous le voyez ici. C'est celui qui prend la préséance s'il était de recoder. Donc c'est bien.
Bien, ce que nous voulons faire maintenant, c'est tester ce projet pour nous assurer que tout fonctionne comme prévu. Donc ce que je vais faire, c'est que je vais passer au hub de débogage.
Écoutez-moi développer cela un peu. Et donc ce que je ferais, c'est cliquer et commencer à travailler. Voyons ce qui se passe.
D'accord. Donc une page de navigateur s'ouvre. Et si vous obtenez cette page ici, eh bien, ce que cela signifie est assez normal, cela signifie que nous n'avons pas le certificat auto-signé qui accompagne dotnet, il n'a pas été approuvé. D'accord ? Donc il y a un moyen très simple de contourner cette décision et d'approuver correctement le certificat qui accompagne dotnet. Donc laissez-moi simplement fermer cela, et arrêter. Et revenons au terminal. En fait, laissez-moi ouvrir un nouveau terminal ici. Oui, le voilà.
Pour approuver un certificat qui accompagne le SDK dotnet, ce que vous devez faire, c'est simplement taper dotnet dev-certs https
trust.
Lorsque vous exécutez cela, vous obtiendrez cette fenêtre contextuelle, vous demandant de confirmer que vous souhaitez vraiment approuver ce certificat, je vais dire oui. Et puis cela devrait le faire. Donc je vais l'exécuter à nouveau.
Voyons ce que nous obtenons.
D'accord, donc oui, nous n'obtenons toujours pas grand-chose ici. Mais nous ne voyons plus de problème de confiance. Maintenant, si ce que nous voulons voir quelque chose de significatif ici, bien que cela n'ait pas trop d'importance pour nous à ce stade, ce que vous pouvez faire, c'est simplement aller à swagger.
Et vous obtiendrez cette belle interface utilisateur. Donc c'est ce que nous appelons swagger ou aussi une spécification OpenAPI. Donc c'est un composant qui est maintenant intégré à dotnet five, donc vous n'avez rien à faire pour le rendre disponible. Comme vous l'avez vu, nous n'avons rien fait. Donc il est juste là. Dans l'URL slash swagger. Ce que cela fait, c'est qu'il permet de décrire facilement toutes les opérations, toutes les actions, toutes les routes qui sont disponibles dans votre API, et permet également d'interagir facilement avec elles. Par exemple, si je vais simplement à la route get, et que je clique sur trade out, cliquez sur Exécuter, il ira de l'avant et l'exécutera. Et nous pouvons déjà voir certains résultats pour cette route.
Et une chose que j'aime faire lorsque je travaille sur ces projets, c'est de ne pas ouvrir le navigateur à chaque fois que j'exécute le projet. Donc allons-y, changeons un peu le comportement de VS code pour que nous gardions simplement cette fenêtre ouverte. Et ensuite, chaque fois que nous appuyons sur f5, ou que nous cliquons sur Exécuter, il n'ouvrira plus de fenêtres. Pour ce faire, je vais m'arrêter ici. Et nous pouvons aller à launch.json et fermer cela.
La seule chose que vous avez à faire est de supprimer cette section ServerReadyAction ici.
Et c'est à peu près tout. Si je relance, et au fait, vous pouvez le faire en appuyant simplement sur f5,
que je vais faire maintenant, cinq.
Cela démarre l'hôte, comme vous pouvez le voir, mais nous n'avons pas ouvert plus de fenêtres, ce qui est bien. Parce que nous avons le navigateur prêt à appeler juste là.
Ensuite, la dernière chose que j'aime faire en termes de configuration de pratique est de simplifier la façon dont nous construisons notre projet en morceaux pour enregistrer. Pour cela, laissez-moi minimiser cela et revenir à Visual Studio Code. Arrêtez cela, fermez cela, ce que je vais faire, c'est aller à tasks.json. Et la seule chose que je vais faire est d'ajouter cette petite section ici sous la tâche de construction, qui est simplement ce que nous appelons le groupe, qui va construire et il dit est par défaut vrai. Ce que cela fait, c'est qu'il nous permet de construire plus facilement un projet. Donc maintenant je peux aller à, laissez-moi juste sauvegarder cela. Je peux aller à terminal, exécuter des tâches de construction et construire immédiatement. Sans cela, nous sommes capables de simplement avoir une fenêtre contextuelle encore un autre menu pour faire la construction. Je peux aussi maintenant faire Ctrl Shift B et cela fera la même chose. Oui, cela accélère simplement notre situation.
Dans la deuxième partie du tutoriel sur l'API REST .NET 5, nous avons introduit les fondations de notre API, qui incluent l'entité centrale utilisée pour représenter les éléments de votre catalogue, la classe de dépôt responsable de toutes les opérations liées au stockage des éléments, et le contrôleur qui gérera toutes les requêtes envoyées à une API REST.
Ici, vous apprendrez comment modéliser une entité via les types d'enregistrements C#.
Comment implémenter un dépôt en mémoire.
Sources
et comment implémenter un contrôleur avec une route GET pour récupérer des ressources.
D'accord, maintenant que nous sommes à l'aise avec la configuration initiale de notre projet, il est temps de commencer à configurer les entités et un dépôt qui sera utilisé pour stocker et récupérer les éléments qui seront utilisés dans le service. Donc la première chose que nous allons faire est, eh bien, fermons simplement ceux-ci et le terminal. Et débarrassons-nous de quelques-unes de ces classes qui n'ont vraiment aucun sens pour notre projet. Donc je vais simplement supprimer la prévision météorologique ici. Et je vais supprimer le contrôleur de prévision météorologique. Donc nous pouvons commencer plus proprement que cela.
Donc, introduisons notre entité, l'entité item. Pour cela, ajoutons un dossier ici appelé
entités. Donc les gens diront domaine, comme beaucoup de gens peuvent voir des modèles, dans notre cas, les entités devraient être suffisamment bonnes. Créons un fichier ici, appelons-le
item. C'est Oui.
Suivons le bon espace de noms ici.
Donc la page suivante devrait être catalog comme il porte le dossier ntds, disons entités.
Et puis
ce que vous feriez habituellement à ce stade est de déclarer une classe, n'est-ce pas, donc vous diriez public class et disons-le. Cependant, depuis .NET 5 et C# 9, il y a une nouvelle option ici pour vous, que nous appelons les types d'enregistrements. Donc les types d'enregistrements sont presque des classes. Mais ils ont un meilleur support pour les données immuables, les modèles de données immuables, n'est-ce pas, ce qui signifie que une fois que vous obtenez une instance de cet objet, il n'est pas possible de la modifier vraiment. Et c'est assez pratique, surtout pour les objets que vous recevez par le réseau. Donc tous ceux-ci proviennent du web, généralement, vous les prenez et faites quelque chose avec eux, mais vous ne voulez pas les modifier. N'est-ce pas ? De plus, les types d'enregistrements ont cette chose appelée expression with que vous allez voir plus tard dans ce tutoriel. Et aussi une capacité à comparer sur la base d'une valeur. Donc l'égalité basée sur la valeur est ce qu'ils appellent, ce qui signifie que
quand vous comparez deux instances de ces éléments, dans ce cas,
ces instances seront égales, seulement si toutes les propriétés de cette instance sont les mêmes, par opposition à juste l'identité de l'objet lui-même, ce qui serait le cas des classes. Donc les types d'enregistrements sont assez pratiques, je pense qu'ils sont une excellente option pour les objets que nous allons utiliser ici. Donc
changeons la classe pour l'enregistrement. C'est le moment d'introduire les propriétés pour cet enregistrement. Donc ajoutons quelques propriétés ici. Donc voyons. Donc utilisons un bon pour notre ID.
Et laissez-moi importer cet espace de noms système, l'espace de noms système manquait ici. Et avant d'ajouter plus de propriétés, laissez-moi faire un petit changement ici. Au lieu d'utiliser Get set, passons à init. Pourquoi l'avez-vous vu ? C'est une autre addition dans C# 9 dans .NET 5, qui est un excellent ajustement pour,
pour les initialiseurs de propriétés. Où nous voulons seulement permettre la définition d'une valeur pendant l'initialisation, n'est-ce pas ? Donc cela signifie que, par exemple, dans le passé, vous pouviez dire que vous pouviez dire, Get set. Ce que cela signifie, c'est qu'après avoir créé l'objet, vous pouvez simplement modifier dans ce cas, l'ID à tout moment. Donc ce n'est pas vraiment souhaitable, nous voulons une propriété immuable. Donc pour immuable, nous devons définir
privates, n'est-ce pas ? Donc oui, cela le rend immuable. Mais maintenant, il est assez difficile de construire un ensemble de ces objets. Maintenant, nous devons introduire un constructeur. Et nos clients n'ont pas vraiment un moyen plus agréable de parcourir nos objets. Donc pour trouver un bon terrain d'entente, ils ont introduit l'accesseur init ici. Donc cela signifie que
vous pouvez utiliser une expression de création pour construire cet objet item, comme vous le feriez avec un set. Mais après la création, vous ne pouvez plus modifier cette propriété. C'est un très bel équilibre entre les deux mondes. Je verrai comment cela se passe plus tard. Donc à ce stade, définissons simplement nos propriétés. Donc voyons. Ajoutons bien sûr, le nom.
Nom.
C'est bien d'étudier le nom
Oh,
Aussi dedans, ajoutons,
ajoutons un prix pour notre article.
Prix, aussi dedans.
Et enfin,
ajoutons
un décalage horaire. C'est
super.
Donc ce sera la date et l'heure où l'article a été noté dans le système. N'oublions pas non plus de changer cela en init.
Maintenant, il est temps d'introduire notre dépôt, qui est la classe qui sera responsable du stockage des articles dans le système. Pour garder les choses simples pour l'instant, nous n'utiliserons qu'un dépôt en mémoire. Et dans quelques épisodes plus tard dans le tutoriel, nous apporterons une base de données appropriée.
Donc, créons un nouveau dossier ici appelé dépôts.
Et ici, nous ajouterons un nouveau fichier. Appelons-le
inmemoryitemrepository.cs.
L'espace de noms ici, n'oubliez pas que ce sera catalog.repositories.
D'accord.
Et maintenant, créons une classe. Donc
classe publique, je vais simplement prendre le nom du fichier principal.
D'accord ?
Nous allons faire ici, comme je l'ai dit, c'est en mémoire. Donc nous allons simplement définir une liste très simple d'articles qui
seront les initialisateurs avec lesquels nous allons travailler. Donc déclarons une liste ici. Donc voyons, écrivons et la rendons en lecture seule car elle ne doit pas changer. Je veux dire, l'instance de la liste ne doit pas changer après que nous avons construit ce dépôt.
objet.
C'est bien d'être une liste d'items
nous avons utilisé pour les combattre.
Et je pense,
oui, donc ces articles.
Et oui, nous devons importer le
quelques espaces de noms, leur collection générique pour la liste. Et cet autre entités pour l'item.
Et nous voulons dire que c'est nouveau.
Et nous voulons déclarer la liste. Maintenant, c'est une autre addition dans C# 9.
Comme vous le voyez ici, dans le passé, vous auriez peut-être eu besoin de dire, d'accord, donc c'est une nouvelle liste d'items, n'est-ce pas ? Mais c'est un peu redondant. Puisque nous savons déjà clairement que c'est une liste d'items. Donc pourquoi tant de cérémonie, donc supprimons simplement cela. Et c'est tout ce que vous avez à faire à ce stade. Belle addition, C# 9.
Donc, ajoutons quelques articles ici. Donc disons nouvel article.
Et faisons,
faisons
l'initialisation ici. Donc faisons simplement un random go it.
Vous allez pour l'ID. Encore une fois, importons l'espace de noms du musée. Et donc pour le nom. Et donc
comme je l'ai mentionné dans l'introduction, j'aime jouer avec ces idées en termes d'objets de jeu vidéo. Donc le premier objet que je vais choisir ici est un avortement. Super classique dans ces jeux vidéo. Et disons que le prix pour celui-ci va être juste neuf. Et pour la date de création. Eh bien, je vais simplement dire
décalage horaire
etc. Maintenant. Donc cela signifie maintenant en termes de UTC, donc pas l'heure locale, juste l'heure UTC.
Et faisons en sorte que nous en ayons besoin, ajoutons un
couple de plus.
Donc même un bon. Je veux dire, obtenez un nouveau GUI pour chacun de ceux-ci. Mais appelons celui-ci. Ce sera un mot d'ordre, resort. Et disons qu'il sera plus cher. Faisons-le à 20. Et pour le dernier, disons que nous allons faire
rose shoot.
Doit être un peu moins cher, disons.
D'accord. Oh, là nous avons notre liste initiale d'articles prête à partir.
Maintenant, dans ce dépôt, nous allons devoir gérer un tas de choses, n'est-ce pas ? Donc comment obtenons-nous un article ? Comment obtenons-nous une collection d'articles ? Comment créons-nous un article, mettons-le à jour, supprimons toutes ces choses. Donc pour simplifier, commençons par un get. Donc nous allons faire deux get metals ici.
Le premier sera get items pour obtenir des items si nous allons faire, nous allons simplement retourner un ienumerable de item.
items. D'accord ? Donc un nominal est assez basique, c'est une interface très basique
que vous pouvez utiliser pour retourner une collection d'items.
Et puis oui, c'est aussi simple que de dire, eh bien, comme le vôtre, de retourner, peu importe ce que nous avons dans les items maintenant, c'est
ce genre de méthode get items. Et le suivant sera similaire. Mais ce gars va retourner juste un item, il va s'appeler get item.
Mais cela devra connaître l'ID de l'item à retourner. D'accord ?
Démarrer. Et puis. Donc pour récupérer le bon item, ce que nous allons faire, c'est dire, d'accord, donc retournons de la collection d'items.
Et je vais dire où ces importations sont encore un autre espace de noms, assises dans ce lien d'importation maintenant.
Et donc nous allons dire, d'accord, donc de cette liste,
où l'item
ID
est égal à l'ID que nous avons obtenu dans le paramètre.
Et cela va retourner une collection. Mais nous ne voulons pas la collection, qui est une, le seul item qu'il devrait trouver, tous les dossiers où le fichier va être ? Non. Donc s'il trouve l'item, il le retournera. S'il ne le trouve pas, il retournera null.
D'accord, donc cela constitue notre dépôt. Et donc la prochaine chose à apporter est le contrôleur. Donc comme je l'ai dit, le contrôleur va être la
classe qui reçoit une requête qui a été envoyée par le client et la gère correctement. Donc ajoutons notre classe de contrôleur. Donc un nouveau fichier sous le dossier des contrôleurs, et
scènes, les raisons que nous traitons ici sont des items. Donc la convention sera de l'appeler simplement. ItemsController.
C'est Oui. D'accord. Et encore, sécurité, espace de noms du catalogue, dans ce cas, il va être des contrôleurs, des contrôleurs. D'accord.
Donc ce contrôleur va être
toute la classe ItemsController. Et puis la chose intéressante à propos des classes de contrôleur est que vous voulez toujours hériter de controllerbase
cela vous donne et cet espace de noms important ici.
Donc cela transformera effectivement cela en une classe de contrôleur. Donc héritons toujours de controllerbase.
C'est la première chose. La chose suivante est de marquer cette classe comme un contrôleur d'API. Juste là. Oui, APIController. Donc cela apporte un tas de comportements par défaut supplémentaires pour votre classe de contrôleur, ce qui facilite simplement notre vie. Donc oui, n'oubliez pas d'ajouter la couche APIController. Et oui, et au fait, il y a des tonnes de documentation sur chacun de ces attributs, ou les
la chose suivante est de déclarer la route. Donc la route définit à quelle route HTTP ce contrôleur va répondre. Et,
par défaut, ce que vous mettrez ici est simplement le nom du contrôleur. Donc si vous faites cela, cela signifiera que quel que soit le nom du contrôleur, ce sera la route. D'accord ? Et donc dans ce cas, par exemple, disons pour un get,
il fera get slash items, n'est-ce pas ? Ce sera une partie de l'URL que nous allons utiliser.
Donc oui, je veux dire, vous pouvez le faire de l'une ou l'autre manière. Ou vous pouvez déclarer explicitement une route que vous voulez utiliser, comme, je pourrais dire, deux items, ce qui est parfaitement valide. Et en fait, restons avec cela.
Et continuons.
Maintenant, ici dans ce contrôleur, bien sûr,
pour toute action, ou toute opération que nous allons faire, nous allons avoir besoin de ce dépôt sur lequel nous avons travaillé il y a un instant. Donc apportons une instance de cela. Nous allons déclarer private readonly.
Encore une fois, parce qu'il ne sera pas modifié après la construction.
IItemRepository. D'accord. Et
Oui, pour tout espace que nous avons prêt. Maintenant, avant de continuer. Laissez-moi simplement vous dire que nous allons faire quelques choix pas si idéaux en avançant. Juste pour expliquer que
Un tas de concepts, n'est-ce pas ? Donc le fait que j'introduise une dépendance explicite dans i ng meme it suppository.
Ce n'est pas idéal, mais je veux juste le faire pour garder les choses simples et puis améliorer au fur et à mesure que nous avançons. D'accord ? Donc ne prenez pas cela comme une parole finale sur la façon dont vous devriez faire cela. Maintenant, nous allons construire ceux-ci dans le
dans le constructeur de ce contrôleur. Donc ajoutons un
constructeur de contrôleur public.
D'accord, et puis disons oui, donc double story
est égal à nouveau nom. C'était, n'est-ce pas. Donc boom, nous avons un dépôt prêt à être utilisé ? Donc définissons notre route d'état pour trouver une route. Donc voyons. Comment trouvons-nous une route pour récupérer tous les articles ? Sur ce stockage ? Déclarons une méthode appelée
Hi enumerable.
Item ? Désolé, item, get items.
D'accord, donc ces méthodes vont retourner un ienumerable d'items comme le dépôt. Importons les bons espaces de noms. En manquons-nous ici ?
D'accord.
Mais alors aussi, pour que cette méthode devienne réellement une route et réagisse à un verbe HTTP, vous devez la déclarer correctement, l'attribut correct dans ce cas, HTTP GET est ce que nous voulons déclarer pour cela. En faisant cela, cela signifie que lorsque quelqu'un effectue un get contre slash items, c'est une méthode qui va réagir à cela, n'est-ce pas ? Je vois comment
quelques cas nous avons presque la même route, mais c'est le verbe qui fait la distinction dans quelle méthode va être invoquée. Alors que faisons-nous dans ce HTTP GET ? Donc assez simple, nous allons dire, d'accord, donc nous avons des items qui vont être un dépôt.
Vous vous souvenez, nous avons créé cette méthode get items. Donc nous l'invoquons simplement, nous avons les items, et puis nous retournons les items.
Et c'est tout.
D'accord, donc avec cela, nous devrions être capables de tester cela et voir comment cela se passe. Donc je vais appuyer sur f5 ici.
D'accord, aller à swagger. Et je vais utiliser la page de rafraîchissement que nous avons ouverte à partir de la vidéo précédente. Comme vous pouvez le voir, tout a été rafraîchi. Il n'y a plus de contrôleur météo. Maintenant nous avons un contrôleur d'items. Et nous avons notre première route ici, nous utilisons un get pour les items.
Non seulement cela, nous avons aussi un schéma qui décrit un
item, comment il sera disposé. Si nous cliquons sur items. Et voyons, essayons-le. Voyons ce que nous obtenons.
Et juste ici, comme vous pouvez le voir, swagger montrera le
router boot exécuté, comme nous l'avons dit, c'est un get sur slash items. Cela
est ici, c'est notre hôte et le port. Donc contre localhost 5001 items. Et
here's the result. So we have our three items that we declared as static. Well, the initial value for our items collection, right? Or the potion, the sword and the shield, right?
This is working great.
Stop this and add our second route, which is a route to return one specific item. So this is going to be public
item, get item and then we receive good, right?
And then are we missing any space? Again, let's see. Yes, focusing in a space. That's where it is. And then just like before, we just say, well, var item equals repository
dot get item. Okay, so here's the other method that we added to the repository, we just pass the ID, and we return that item.
Now, again, we need to mark a top like that right bear here. So it is going to be an HTTP GET. But in this case, there's a little additional detail, which is the template. So we have to provide a template where we specify
how we're going to treat another piece of the route. Like in this case, the route is not going to be just get slash items. It's going to be good slash items. And the the idea of the item, right, which is is that piece of the big plate, I was just going to put it like that. So would you request the items is slash rF cific item ID then this
This piece is going to get executed. Right?
So yeah, let's see how that goes. I'll do f5 again.
Okay, this is running back to swagger. Got to just refresh the page. And as you can see, now we have our second route available. Like I said, this is slash items slash ID. So, in order to execute that, let's Well,
let's actually execute the first one items to get a list of all of our
items that we know about.
And let's grab the first one.
so dearly, we should be able to get that item via the the other route. So gateway ad, so let's try it out our basic item here, so these how in open API swagger, you can introduce values, right? And now execute that, see what happens. So here's the executed route. So you can see slash items and the slash the actual ID. And then
interestingly, we are getting 204. Right? So this means that something didn't go quite as suspect.
What I'll do is I'll go back to Visual Studio code, and I'll put a breakpoint over here, see what we're getting. So go back to swagger, UI, and execute.
Here, we got a breakpoint. And, right, we're getting No, that's what's happening. So the item has not been found. So why could why would that be? Well, let's see. Here's our items. And here's our get item. I made that.
And so, yeah, so really, what was happening here is that anytime we make a request to this to our controller to our, to our service, we are actually creating, as you can see, over here, a new instance of the in main items are positivity. Right? So that means that he's created, a brand new list is created with a random set of new codes here. And of course, when we try to use our previous use it good, it will not find it, because now we have a brand new list.
So I'm going to do a five, yesterday go and
this is a good actually a good thing, because you have to realize that we need to deal with that situation properly in in the controller. So in this get item method, we should be a not just returning an All right, so how do we handle this? So let's stop it.
So I think that we probably want to do is to return the proper status code HTTP status code in the case that we can find the ID. So
let's say so if item
is no all right, so let's return it something different. So let's return
not far. So that's the way to actually ask a dotnet to create the proper status code for not found so we don't have to actually figure out so I set a status code.
And then yeah, if it is found, they will just go ahead and return the item. Now we do have a problem here. Because now a in one branch, we're trying to return this type, or not found result in these other graduates right to return just an item. So what is it going? I mean, it's not like in it, right? We expecting to always return an item. How do we deal with this is a by the use of the action result type. So if we do action result,
that actually allows us to return more than one type from this method. So as you see, there's no more errors, because now this is saying, Okay, if if if you want to return not found return not found, or if you want to return the type that is in the a direct tie right here, you're also able to do that, right? Or we're going to also say something like, Okay, if you wanted to, that's probably fine. But now in this case, we can handle both cases. So let's run this again, and see what we get used to get the proper status code for each of these actions to be properly restful. So let's refresh this. Let's again, let's get one of these items are going to try it out.
Execute.
So good. Yeah, can you just get one of these we know it's no good to find it. But get that good. And then open up here, try it out
with ad and execute. So this time, we do get a 404, which is the correct status code word for not found, as you can see here,
in this third part of the dotnet five REST API tutorial
We learn about the dependency injection technique, and how to leverage it to properly inject a repository instance to the items controller. We also introduce the concept of data transfer objects, and how to use them to establish a clear contract with our API consumers.
Today, you will learn what is dependency injection, how to register and inject dependencies in dotnet. Five,
how to implement data transfer objects, also known as dtos,
and how to map entities to details.
In a previous video, we were able to actually create our entities, repositories and even our controller to be able to get items and our specific item. However, we find an issue where we try to retrieve an item, we can retrieve it, because as we found and just go back to the code,
anytime we receive a request, in our items controller, we are creating a new instance of depository. And that's bringing a bunch of new items in such a way that we are never able to find, right. So how can we go around it? I mean, how can we actually fix this the right way?
So for these, there's a pretty important concept that we need to learn here, and which is called dependency injection. So let's talk about that.
So what is dependency injection? Let's think about our class, right, so we have a class, which wants to make use of some other class, where we have this kind of relationship, we say that this other class is a dependency of our class, right. And, in more concrete terms, in our case, we have the itis controller, which is constructor is a creating a new instance of the repository ready in meme items repository. Now, what we really want to do in terms of dependency injection is flip things a little bit. And instead of have a test controller construct that instance, and I'll just open up my highlighter here,
we will receive the repository in the constructor, and then just take that, that, that reference into the class. So at this point, we are injecting the repository dependency into the IDS controller class.
Now this is also brings in something very important, which is the dependency inversion principle, in which again, so we have a class, and we have some dependency, let's call it dependency a. And well, this class depends on dependency a, but what we want to do is just not take that kind of dependency, and instead, have our our class depend on an abstraction, which is, in this case, in C sharp, it is an interface, right? So the class no longer depends on dependency, it just depends on some interface that dependency a will implement. Right? So we have inverted the dependency by having a class only depend on an interface and dependency a implement that interface.
At the same way, I mean, as we do that, we could bring in a dependency or any other dependencies that also implement interface. But in this case, you can imagine, now the repository that I just control receive is just an interface. So class in this case, has no idea of which explicit dependency it is working with, could be a B or any other dependency, as long as they implement the contract, which in this case, is the interface. And this instruction class is very happy to work with it. Okay, so that's, that is the dependency inversion principle. And
well, the thing is, okay, so why why do we want to do this? Well, really common reasons. And yeah, like I said, like he says, right, so by having our codependence dependent upon abstractions, we're decoupling implementations from each other. So it gives us much more freedom in terms of moving around these dependencies, without ever having to touch our class. And this makes the coal cleaner, easier to purify, and much easier to reuse. But I'm very wary, so much easier to test.
But then, if we're going to do this, how are we going to construct these dependencies, right, because now we're just receiving them in the constructor. So if we have all these dependencies, dependency ABC, how are we go the constructor constructor. So because our class was to receive them, right? They were going to inject them there. So comes into play this thing called a service container, which in terms of a dotnet five is an AI service provider.
So what happens is that a during a application application startup, we're going to register, each of these dependencies are going to be registered into the service container.
And then eventually, when the class gets instantiated, the service, a service provider separate container is going to take care of resolving any of the dependencies needed by this class, like it has a map of all the dependencies are needed by each of our classes. So resolve the dependencies, construct them, if needed. Only the first time, of course, depending Well, actually, depending on the application lifecycle that has been set up for those dependencies for the class. So if needed constructors, otherwise, it will reuse it. And then it rejects the dependencies.
And this is, in fact, what's going to help us with little problem that we have right now in the inner project where we want to be constructed and constructing I mean, we don't want to get a split dependency on the regulatory, or we don't want to construction, explicit construction of warnings as every time we created the controller, we just want to receive an East as if it's available, a lot of constructed getting constructed first time and get it all sorted by the service container.
Let's see now how we can use dependency injection to our advantage. So what I'm going to do is I'm going to get back to the code. So let's stop debugging close terminal. And so let's fix the situation where we have this explicit dependency on email it depository. So firstly, we're going to need is some interfaces so that I just controller does not really a operate on concrete instances of the depository. So let's go to a main idea posit Ori, and we're going to do is just right click on the class and
actually in the light bulb, and let's extract interface. So that's going to create a interface for us and makes it makes it possible to implement that interface. Now, we probably want to take the interface out into its own file. So let's do that.
A new file,
let's actually call it a I items depository, should be a better name for this.
Okay.
And so
thanks, space catalog, dipole stories. And here's where we're going to bring in our interface, I'm going to use cut
and paste here. It is a report a couple of nice spaces, let's see, for the entities.
For CS for good. Collections generic, that should do it. Alright, so we have our interface. And here's the repository.
repository implements interface. So now that we have the interface, let's go back to our controller. And
let's switch this into I mean, this type into i a items of repository
of policy
items.
For seat gaudy.
Make sure this is off. Yeah, we have to do the right naming here for that
game. And now we've got the receive it here. So I demonstrate depository. This is a boss story.
And then just to not confuse things, let's say this, that repository equals depository.
Alright, so yeah, so now we got the pennsy injection working here. And no longer This class has any idea of which repository is going to use behind the scenes. Now, the only thing we have to do here is to
what's going on here, actually, I was just fixing that in our laboratory. The other thing is that we have to do the registration, right. So to do the hyperinflation of our depository, or we're going to do is go to start up, configure services. So this is the place where you register all the services that are going to use it across your service. And the service that we need now is our history. So let's do services that
add Singleton. Now there's a bunch of ways to add a URI. To register your services here, I'm going to be using a singleton so and a singleton is nothing, nothing else, other than just having one copy of the instance of a type across the entire lifetime of our service. So only one will be created. And it will be reused wherever whenever it is needed. So that's going to help us resolve the problem that we have today. And so to add the singleton fares, we specify the interface.
So
I added repository, which we may need to add. Yes, we namespace. And then so that's the interface. And then the concrete instance is
in main items repository right?
That's it. So that's how you register your dependency. And so
at this point, we should be ready to be ready to go. So I'll do a five.
And now I'll switch to swagger.
So we have the same API as before, I'll refresh anyways. And then let's try an exercise now. So let's see, I'll try out our items, endpoint. So I'll get one of our items.
And I should be able to find it now. So see, items, Id try it out. I'll put it here and there, let's try to find it execute.
You have a breakpoint here. Let's see. Yes, this time, we can resolve no more, no, remove that breakpoint, run back to swagger. And then here it is, we got our response code 200, for the request of that item, and here's all the body that is crucial.
So
as expected, now we only have one copy of repository hanging around, which is injected into the controller, and that allows us to actually find the data we're looking for.
Now, there's one more thing to notice here that we should fix right away. And which is the fact that these
these routes that we have enabled right now are enabling a or are exposing our item entity directly to the outside. And we have to understand that as we build REST API, we're also establishing a contract with any of the clients that we're going to be using, which is a contract that we should not be breaking easily. And the problem that we have right now is that since we're exposing item, which is the item that we're using for dealing with persistence, we have versatility.
Anytime we want to add feel, I mean, anytime we want to modify or remove any of the fields that were that we're using in our inner storage, right now, in the versatility, we can potentially break our clients, right, break that contract, which is really no goal for us to build these REST services.
So how can we avoid exposing these item contract there? So let's, let's take a look, let's go back to the project. Let's actually find that entity. So we have item here. And as we said, we are returning it both in get items and get the item. So what we're going to do now is introduce what we call a DTO or a data transfer object. So data transfer object is nothing else other than the the actual contract that's going to be enabled between the client and our service. And to do that, we're going to do introduce a new folder here. Let's call it videos.
And let's add a new file for our item dt. All right. All right in the deal. So again, namespace catalog, in this case is going to be videos.
And the identity is going to be fairly similar
to our item, actually.
So let's want to just copy
the item.
And so
going to add missing spaces.
here and there. Yep. And, yeah, I mean, in this case, it happens to be that the item that we want to return in our methods is pretty much the same as the item that we will be storing in a repository or retrieve the depository. So we use, okay, seems a bit redundant right now. But the benefits become evident as you move forward as you start modifying your database. You don't have to be touching this contract, or you can be very careful with the contract, or supposed to be breaking our clients anytime. So this gives you a lot of flexibility as you evolve your data store.
So now that we have, actually let's rename this to item DTO. Sorry for that. Okay, I think the deal. And now that we have that, it is time to start using it right, so let's go back to iTunes controller.
And so at this time, what we'll need to do is to convert the iTunes or we're getting started with good ideas, right, we have to convert these in from item into identities. One way to do this would be to just do a simple projection with link. So we would do a select
and then maybe missing the link a space here to add it. I'm going to say okay, so I tend to
project into a new item DTO
and I may need to add any space here. There it is.
So here we're going to bringing the properties, right, so I'm going to say alright, so Id is equals item.id.
name equals item name, same thing. Price.
Okay, so now we have a our items collection is a collection of items, we do
think we're missing parentheses here.
And we return
those items, no longer item, there has to be item B to
setting up the contract. Okay, so, yeah, so that should do it, we have to inform the item, do it and do. And as you may guess, we have to do pretty much the same thing over here, right?
This point, it will be a bit redundant, right? So why will want to do this transformation twice with exactly the same properties. So one way that we can overcome this is by adding an extension method. So let me show you what I mean by that. So I'm going to add a new file here, we're going to call it extensions.
Games. So good luck. And then what extension does addition metal does is it just will extend the definition of a one a type by adding a sum a method that can be executed on that type. So in this case, we're going to add a class only static class or for extension methods, you have to use a static class, there's a way to go,
extensions.
And then we're going to declare one method here. public static is going to return item DTO.
And we're going to call it as DTO. What is going to it's going to operate on the current item
does, that's what this method means. So again, let's add some namespaces here.
So this method receives an item right, by by using this here, it means the current item can have a method called as DTO, that returns its identity or version.
So at this point, we can probably take advantage of what we did here. So let's see, this is what we used to create activity Oh. So we can say well return new identity or out of the item that we received.
Ladies, we have an efficient method ready to be used. So now we will go to adjust controller, what we can do is instead of all of this, we can say so item is projected into it, and that as the deal.
That's all it is. Let's collapse this a bit.
With that, we can also use the same method over here. So when we get the item, we will say as you do, of course, we need to change this into our do contract.
And then the rest. Actually, I will do this. So let's just get the item first. Check if it is no, if it's not, then we do the CTO.
Okay, so now that we have done that, let's see how that goes. So I'll do a five again.
Okay, back to swagger.
And I'm going to refresh this. And let's see if this still works. So items, I'm going to try it out. Execute. And yeah, just before we can get the list of items. But this time, if you scroll down, you will see the disclaimers. The contract we're exposing to our consumers is no longer an item but it is item to do with the properties posted right here.
In these four parts of the dotnet five REST API tutorial, we introduce additional controller actions for creating, updating and deleting items. We will also learn how to validate the incoming dtos to prevent invalid data from landing in the service.
Today, you will learn how to create resources with post how to validate the values of DTO properties,
how to update resources with good
and how to delete resources we delete.
It is time now to introduce the rest of our, our routes, right so route for both to create an item, route for update, delete, or update the item and the route for Delete to be able to delete a date.
So let's start with post. Right. And before doing anything else, what we're going to have to do is to update our repository to be able to have that create that route for creating an item right
So let's do that. And I'm going to start again, by going to the interface, that's the first thing to do. And so let's declare
void, create item,
item item. Okay, so this new method just returns nothing. And it only just received the item that needs to be created into the depository. Now let's switch back to the concrete in main, it depositories. And I'm going to say, implement interface. So that brings in a new method right there. And CNC turning memory depositories, this is as straightforward as just saying items that add
item.
That's all it is.
And then now we want to expose these into the controller, right, we start to add a route the controller. But before doing that, we got to realize that so the client is going to be sending these, these items. And we will have to establish another contract for receiving that, that item, it cannot be identity or because we don't need as many properties as in identity or for the creation of an item. So let's see what I mean by that. So I'm going to go to our details folder, I'm going to create a new file, let's call it
create identity Oh,
and add the space
catalog that videos. And then.
Okay, just like before, we're going to be using record for this pretty convenient for the tiers. And so now I've looked back at it to, let's see, so what will make sense to be sent by the client as we create an item. So normally, the ID is auto generated in the server side, right? So we don't need to be passing in that ID, we do need a name and a price. And likely the created date is going to also be generated on this service. So we're only going to include in these two
lists. So we have name and price. And that will be for our create item DTO.
And now let's see how we can use it in the controller. So back to the controller.
Let's see how we declare these a post route. So it's going to be
public action result. Because again, we could return more than one thing in this method, things could happen. And
they call the convention for a post or a create method is to create the item and return the item that got created. So if we're going to do that, we're going to we should be fine to return the identity. Now that the conventions here are going to vary. So some people will create their own response object here, it doesn't have to get into to it happens to me that it works fine for us in this case. So that's okay. So it's going to be called right in
grade eight MBT Oh, that's going to be our input contract. Item BPO.
Okay, and then let's qualify these with the right verb. So each HTTP POST, and then just for the communication will say so yeah. So this is going to be invoked when somebody does a post into the items route with the correct body, of course.
So how do we create an item? So very straightforward, we're going to say, well, item,
item equals new, we will have to find the type. So thanks for sharp nine.
And then yeah, so let's just say ID equals, here's what we actually
generate the ID for the item. So it's going to be good, that new good.
So the name is identical to that name, same for the price.
Price, and then the created date, as as you would expect
a time offset that UTC now,
that's the item. And now we have to build we'll take advantage of the repository method we just created.
Create item, and then it goes there.
And then once the item has been created, a convention here is to Yeah, I mean to return the items that were created and also a to return a header that specifies where you can go ahead and get information about that graded item. So to do that, what we can do is use
Created add action, you can also use create route, that's another way to do it. gradient action will work fine for us. Because what we can do here is say, Okay, so what's the action that we want? What's the action that reflects
the route to get information about the item. And that's going to be our get item action right here.
So what we can do here is say, well, there's going to be the name of get item.
And then when you specify the ID, that's going to pass it to that order route. So for that, let's create just a simple anonymous type here.
With ID equals ID and ID. So that's a generated ID. And then finally, the actual object that's going to be returned, which is a thing. And again, let's take advantage of our extension method as DTO. So we take the identical created, and then we just converted yesterday to.
And yeah, that's it. Let's do f5. See if this works.
So back to swagger. Let's refresh this. So now we have our post route over here, as you can see, and also our grade identity is showing up as a new contract that we're exposing torque lights. So let's go to post. Try it out. And as you can see, now we only need to provide a name and a price.
Let's see for the name. But let's see, let's bring in something like another type of support, I guess, for let's see, a
de neumes sort to be a bit expensive, let's say 35.
And yeah, so let's start let's execute,
see what we get. So as you can see, here's the request URL the same as the get. But in this case, it is a post, we have guarding 201, meaning a created art route. So he got created a new sort. And as you can see here, we have a location here that specifies Where is that we can find that item.
So if we actually take this a these code here, we go to our collapses and go to our get route. Go to try it out. We're going to paste that here. Execute. As you can see, now this is the route that was provided in the location here for post. And it actually is able to find a developer a new source we just created. And in fact, his use case, get the full list of all the items. Try it out, execute. So now we don't have your three we have four items, including the deadliness.
But then let's also try one more thing. What happens if I try to create something without a name? Does that make sense? Well, let's try it out.
There's the V that that was accepted. And now we have an item with a no value, which is totally unacceptable. And and again, in fact, we use code to the get route and we say, execute, we can see
we have an item with no, which is pretty bad. How can we protect ourselves against that situation. So there's this thing called a data annotations, which is something we can add to our DTO in this case, to prevent that situation. So back into create identity, oh, what we can do is just request that this field is required.
And I'm going to add the data annotations namespace in there. So it name has to be provided, and price has to be provided. For price, don't just add, let's do one more thing, let's say
there's going to be a range of possible values for price because we should probably not accept a negative value or even zero here. So let's say that we're only going to set values from let's say, from one to 1000, that should be a valid range for us. So just by doing this, we are protecting the values are going to be coming into the controller. So I'm going to do a five again.
And let's see how that goes. So back in swagger,
I'm going to be collapsing this route. So back in post, I'll just try to do the same thing again. Try to execute this. And this time.
Now we have a 400. So bad request error. And clearly it says here the Name field is required. So now data validations are coming into place. And so we must provide a name. Let's actually play with the price here. Let's say I try a negative number. Right see what happens.
If you have price must be between one and 1000. Right. So data notations for valuations pretty useful for relating dot r or D do
Now it's time to implement our update route. So let's go back to source code, close terminal.
And just like before, let's go back to our a repository, I identify the interface and add the relevant method. So let's say this is going to be a void. Again, update item. And this is going to receive the item to get updated.
Very similar to create item. Now, back to the concrete implementation.
I'm going to say, again, implement interface
that brings in the method data item. So how would we update this item. So since it is an in memory list,
really, the only thing that we have to do is use find the relevant item and update it with the incoming item, right. So to do that, let's do this is find the index of the relevant item. So I'm going to say items, find index. And then so this is the existing item.
So you're going to find the existing item where existing item ID matches
item.id.
Okay, so that's just finding the index of the of the ID that we're looking for. And we've found it, we can do items, index equals item.
That's all it is. So we will update the item in the right location. So it's time to go back to the controller, well almost like to go back to the controller, because, as you may realize, at this point, we do need some video to receive the input for the update route. And even when that disables, you're going to be pretty much the same as with great identity, oh, these are good practice to actually have a another a video for this case, because you don't know right now is the same thing. But eventually, it could be that an update means something different than accurate. So let's use to update identity Oh,
that's a copy of great identity, oh, pretty much has the same properties is the only thing that you can verify the name and the price. And required and the range, right. So now let's go back to the controller, right this controller,
let's create out of the drought.
So this is going to be again,
public action result.
In this case, a convention for output is to actually not return anything. So use what we call no content. So it's going to be no type here other than action result, it's going to be called update item, we receive two things, the first thing is the good of the idea of the item. And then they are new update item DTO.
Let's call it item DTO.
And then, let's not forget to add the correct verb here is an HTTP put.
And just like we did before, the route is going to be just for documentation, when you to put into slash items, and then slash that's the piece that we're missing actually here, we need to also specify here the template
which is in this case is the ID. So that means that when when we do a put, we have to certify the ad in such a way
like these, right, so put items slash the ID, and they will hit this method here. So let's see. So first thing, so how we will do an update, first thing to do is a Find the Item. So existing item, it will use a repository for that we already have a method for this, which is get item
passing the ID. And then it will be great to verify if this ID if these items actually exist. So if an existing item is no, then well, we couldn't find it. So we will just return not found.
And at the end of story for that that branch.
And then if we find it if we found it, well, we're going to do is just proceeded to do the
we're going to create a new item, which is the updated item in our system. So in this case, we're going to say is item updated item equals it essentially our existing item.
But with
a couple of differences. Like we need to use a the name
of the provided identity. Oh, and the price of that also provided identity. Oh
So, now here, I just use one 190 of a record types that I mentioned when I was talking about record type, which is the width expression here. So what's happening here is that we're saying, Okay, so we're taking this existing item here, are we creating a copy of it with the following two properties modified for new values. So that's a very nice addition into
records, and allows me to use what is really an immutable type. But still, I can go ahead and modify a some properties on initialization. So if the item is just a copy of existing item with a bunch of properties, like nice addition, in record types, where we have this updated item, we can go ahead and say, okay, so repository that updated the method that we just created, and send the bladed item. And like I said,
the convention is to return no content, so nothing to report just returned.
Let's try this out. f5.
And back to swogger refresh.
So here it is, our put
our port route. And before doing a port, let's actually get a one of our a items.
So try it out, execute. So let's say we're going to modify our potion here.
Okay, so the potion, I'm going to collapse these up and put, so for put, we have to provide an ID, and updated values here. So this is a potion, let's actually rename it to, let's say super potion.
Let's bump the price to I don't know, let's say
- So execute.
And then just as expected, we get 204, which is no content. And you can see the route that was executed here. And then if this succeeded, we should be able to get a
updated version there. So I just get the full list of all the items that we have. Now let's see what happens. And there is no longer potion super potion with updated price.
Also, notice down here that we do have a this update identity are available now here, the new contract.
So we'll update really, the last thing to add is our delete route. So let's go back to controller. So back to the project. Just before, back to the repository interface. Let's do void. The title,
we already already know we need for the reading item is just to know the idea of it. So back to the repository.
let's implement the interface
for delete item.
And actually, the item is going to be very similar to update item, first thing that we have to do is find the item, I mean the index of the item. And now we can just say items that we move at.
Look at
that index.
That's all it is. Sorry, we have to say it here.
We had the opposite already. And now we can look forward to the controller.
In this case, we're not going to need another DPO. Because the only thing that's needed here is to use a simple ID. So let's use implement the controller action.
So public action result.
Delete item, just like with update, we're going to return no content. So action result delete item, good ID.
And let's add the verb. So this is going to be an HTTP delete. Again, documentation. So these are going to haunt or delete slash items.
And then slash, let's not forget our template,
which is the ID slash items ID.
And to perform the deletion, similar, certainly with similar to update. So let's try to find the item first. And we'll just copy that base
there. So not found if we can't find it. And then we go ahead and to repository, that delete item and it
just like we did before, return, no content
and that's all it is so f5
and by
swogger refresh again. And now you can see we have our delete action available. So let's come to the name of one of our items and see if we can delete it to execute this. So let's try to delete that potion,
copy,
collapse, expand, try out,
put ad and execute. So here's the route. We got 204 is expected, no content. So if I tried to get the items again, let's see what happens.
All you have to, so no more potion habido here.
And yeah, so that will be at the end of our routes.
In this episode of The dotnet five REST API tutorial, we will see how to start our entities in a persistent store. Specifically in a MongoDB database, we will implement a simple MongoDB repository that can replace our existing emancipatory with minimal changes to our service.
Today, you will learn how to implement a simple MongoDB repository, how to run MongoDB as a Docker container,
and how to use postman to interact with the REST API.
Starting with this episode, you will need a couple of other things to follow the video step by step
Docker, which we will use to run a local MongoDB instance.
And postman, which we'll use to interact with the rest API's from Iran.
Now let's think about the scenario as it is right now. We have our user who is trying to manage his items via the browser. And he will do that by reaching out to our REST API. And more specifically, by reaching out to the IRS controller, which is where or all of our routes let at this point. Now, I just controller will interact with the inmate item depository
to manage these items. And the items are actually stored inside the main depository as a simple items collection right.
Now, what happens if the REST API for any reason stops, either eat stops or is restarted, and it could happen, either explicitly or unintentionally. But it is a really common scenario that a service would need to be restarted.
If this happens, of course, our items collection is going to go away, because it's just a collection in memory, right? This is all desired, we need to figure out a way to keep these items alive beyond the lifetime of the REST API.
So what are these, we have a few options. And I can think of first very basic option will be to using files. So you can think of, well, I'll have one file for each of the items in the repository.
But really, the most common option these days would be to use a database. And in terms of databases, we have 1000s of options. But you can categorize them into relational and no SQL databases.
In this tutorial, we will go for a no SQL database. And the reason for this is well, because of the benefits that it offers, beyond the fact that no SQL databases are one of the most popular options these days, there's the fact that you won't need a schema or SQL to interact with the database. I mean, you don't have to learn SQL yet another language here, you can just stick to your object oriented programming. And in our case, today, she's C sharp API's.
And you also have low latency, high performance and these because
there's no need for strong consistency as it as it would be in a relational database. And also, these are highly scalable.
In our case, we'll go specifically for MongoDB,
which is it is a no SQL database, and specifically is a document storage type of database. So it stores a the entities as documents, specifically as JSON documents inside the database. So this is we're going to use for this tutorial.
So you can think that now we will have this database living outside of our race API.
And now, when a controller receives a request, it will actually hand it over to a new repository called a MongoDB. I just have visited it, that's what we're going to create here.
And even if our services stopped and restarted, the database will not be restarted at all, our data will be saved in there. And that way, we can keep our items beyond the lifetime of the REST API.
Before we start implementing our MongoDB repository, I wanted to show you one more tool that you will probably find useful as you interact with your API's and they want to so is postman and you can get it in the postman downloads page. If you had no
done already?
And why would you use postman for. So if you remember, so far we've been using this page, the swagger UI for interacting with all of our API's.
However, the problem here is that eventually you may not want to be opening, you know, web pages do interact with each of your API's. And these API's may not even be in your host, they may be somewhere. So what else were you there may not be a swagger UI. So you do need a way
to interact with them.
Plus, you may need some additional capabilities that are just not available in this page. So that's where postman can help you. So in this case, let me open a postman right here.
So how we can interact with your APIs in postman. So it's as simple as clicking the plus sign here. And the first thing that you have to do is pick a verb that you're going to use. In our case, let's just try I get that you're going to read the request URL, all of those URLs are going to start with your host. And so in our case, let's see what's your host, let's do an f5 in VS code. And as you may recall, our host Ace and is one of the last lines here for the HTTPS endpoint is going to be localhost 5001. I'm going to copy this
back to postman, paste that. And then if you remember, our route, going back to VS code, now, our route is a
starts over route, start with items right, right here. So that will be the route that we want to use in postman. So just type here, items. And that should be enough to perform I get. So I'm going to just going to see click Send.
And then if you get this problem here, this issue that says SSL error, unable to verify the first certificate, this is because of the SSL verification that postman is performing. But this such verification will just not work with the self self signed certificate that comes with dotnet five. So in this case,
what you want to do is just disable SSL verification, and that should be fine. So you can just click here.
And that will run the request. Again, as you can see, it ran the request. And we have the resource over here. Same results that we had in swagger UI before. But now, we are interacting more directly more directly with that API, the same way that we need to get you can do post put and a bunch of other verbs here. We will do so as we move forward with this with this episode of the tutorial.
So now let's go back to VS code.
And I'm going to stop the host here and close terminal. And I'm going to close it with controller.
So the first thing that we want to do is to
implement a new repository
to be able to interact with a MongoDB database.
For that, I'm going to create a new file.
We're going to call it Mongo DB. Items repository.
Right.
Thanks space.
Got a look. depositories
game. So it's going to be called for league
class MongoDB. depository. And just what are your sweet as with our email it suppository, we are going to implement items repository.
We may need to import a namespace here, I will implement the interface.
And just by doing that we have VS code has scaffolded all the methods that need to be implemented to comply with these interface. Now in order to interact with MongoDB, we're going to need a what is called a MongoDB. client. So the client is
is a component provided by the creators of the owners of MongoDB that you can use to interface will be kind of the adapter that used to interact with MongoDB.
So we'll need to inject that a as we as we everything else, we need to inject that dependency into our repository so that we can interact with it. So the first thing I'm going to introduce here is our constructor. So let's see. folic MongoDB is capacitor.
And here is where we need to receive an instance of our MongoDB client.
Where do we get this MongoDB client from? So to do that, we'll have to add a nougat package. Plug the nougat package what I'm going to do is open up a new terminal
and here I'm just going to die
dotnet package MongoDB that driver, enter,
that's going to go to noget, grabs the MongoDB nougat package. And if you go to catalog CS Pro, you're going to see that now we have, we have the dependency right here.
Oh back to the repository, we are able to start doing the injection.
So let's see, we're going to receive here in the constructor is what we call an eye,
Mongo client,
to listen port import the correct namespace here MongoDB driver, and we will call it Mongo client.
And now what is it that we're going to store?
We're going what we want to store here is not really the client, but what we call a collection. So the collection is the way that MongoDB associates all these entities together. So I'm going to declare a variable here.
I wrote it with only
because we only modified in constructors is read only variable, I Mongo collection. And that you have to specify the type that that of the items, the type of the entities or documents, actually in this collection, our type is going to be our item entity.
And his colleague, items collection.
However, before we can get a collection, we need a couple of other details, which is the database name, and the collection name. So usually, like all of your document, yeah, all of your documents are going to be a grouped into collections. And you can have one or more collections in a database. So the first thing we have to add here is the name of our database. So let's just add a constant here
is call it database name.
And probably a good database name here would be use catalog.
And now let's add a collection name. So private string,
collection name.
collection is going to be called items.
Now that we have these available, this go back to constructor,
and we actually close the terminal for now.
And what we can do is the following. So first, let's get let's get an instance, let's create a reference to the database. So I'm going to say I Mongo database database equals Mongo client that get database. So database name. So that will get us a reference to that database. Now we need a reference to the collection. So items collection,
is the variable that we say that we declared before equals database that get collection, and then the type of the item
and then the name of the collection.
And then the good thing about this is that both database and the collection will be created the first time that they are needed. So it doesn't matter which API we use to interact with the database and collection MongoDB. Or I guess the driver will detect if we don't have them, and they will be created automatically for us to we don't have to worry about it.
So we have a few a few methods to implement. So to get started, we'll go for the Create item method. And we'll start implementing one by one and exercising each each one as we move forward in this video. To implement create item, what you want to do is just make use of that items collection. So you can say, items collection that insert one, and then you just pass the reference to the item. So in this case, it will be just item.
So at this point, you may be wondering, where is this database is MongoDB database that we're going to interact with?
Because Yeah, I mean, we have the code here ready to create an item, but we don't have a database. So there's a couple of ways to get a MongoDB database into your box. So you can either install the database via the MongoDB installer, or you can run the database as part of a Docker container.
We're actually going to go for the second one. And the first concept to understand on that side is a Docker image. And
I mean, we're not going to go deep into Docker concepts here where we're actually going to talk about that in a future video. But for now, you can think of our Docker image as a standalone package of software. That includes really everything needed to run an application. This application in our case is MongoDB. So everything is packaged in this Docker image. Then, when we run or when we execute this Docker image, it becomes what we call a Docker container. So is a running instance of a Docker image.
That Docker container is going to run in the Docker engine.
So how do you get this Docker engine into your box. So you just go to the Docker download page, which I can show you now, right here, did you go to this page, you pick your platform, and then you can go ahead and download and install Docker in your box. Then you have a Docker engine.
And you're able to run any of the Docker images available publicly, like MongoDB, or perhaps some private Docker images that you may be storing in your own Container Registry. In this case, we're going to go for the A MongoDB, a public Docker image, let's see how we can acquire it, we can run it. So first thing we're going to do is go to terminal, say new terminal,
we're going to type the following
Docker run,
then we're going to use the dash d dash dash RM modifier This is so that we don't have to attach to the process. So we just let it go. And RM is so that if the image is actually sorry, the container is destroyed after we close the process. So then, we're going to give it a name, Mongo so that we can easily recognize which image is this. And then we're going to open a port, that port is going to be 27, zero 1727, zero 17. So this syntax here means that we want to open, let's say, we're going to open kind of a window or a view into the Docker container. MongoDB usually listens in Port 27, zero 17.
So what we have to do is we have to open some local ports as in the local machine, we have to open some port that can be mapped into the MongoDB port inside the Docker container. So this is the way that you would do it, you could assign actually any other port externally on the left side. But on the right side, you have to point to that to the MongoDB port. Finally, we're going to specify a volume. And the purpose of this is so that you don't lose the data that has been stored in MongoDB. When you stop the Docker container, okay, if you don't do this, then you will lose all that data as you start getting the content. So let's declare this volume MongoDB data is going to be the name
that is going to be mapped into data dB. Here, slash data slash dp is the usual location where MongoDB
is going to store the data inside the container. And we're just saying where we're going to map this location called MongoDB data from our local machine into the slash data slash dv directory inside the Docker container. And finally, we have to specify the a the name of the image, in this case Mongo,
then I'm going to hit enter,
perhaps I can expand this terminal a bit. So the very first time that you run a Docker image is going to pull it down from in this case from Docker Hub, into the machine. So that may take a while depending on your internet connection. But as you can see, there's there's multiple lines here. Each of them represents what we call layers. So each of them has some piece of this Docker image, including all the dependencies. So like I said, this is just the first time next time is going to be blazing fast on as long as you have those people's lives already in your box. So now we have the Docker image both into the box, and even Docker container running. If I actually do docker ps, I can see that I have the Docker image up and running and listening in this port 27, zero 70.
I'm going to close the terminal. And so
what we need to do now is to be able to point to that to that Docker image. So to do that, we need to write a little bit of configuration. So I'm going to open up settings Jason.
So the basic two pieces of information that we're going to need in order to talk to the database are the host and the port. So for that,
I want to introduce a new, a couple of new settings here. And let's call these MongoDB settings.
Like I said, we're going to need a host
and we're going to need a port.
So, in the case of a MongoDB instance that's running in your in your machine, you can refer for the host you can just call it localhost.
And as we said, the port that we opened in the Docker container was 27, zero 70.
Those are the details that we need to talk to them to the to MongoDB. Now in order to read these settings into our service, I mean there's a
bunch of ways to do it. But I think the best way is to declare a class that represents the settings so that we can easily interact with the multiple settings from our C sharp code. To do that, we're going to introduce a folder here, let's call it settings.
ukoliko, call it configuration options. There's a bunch of ways, I'll go for settings. So let's add a file here, let's call it
sorry,
Mongo DB settings.
And, again, namespace, got a look that settings in this case, this declared a class.
Let's call it MongoDB. settings. And here, we're going to declare those settings that we saw in app settings, Jason this, this declared them as properties. So our first property is going to be
prop string cost.
And the next one
public int port, so this is an integer.
And then, let's actually take advantage of this class here, to calculate the connection string that's going to be needed in order to talk to MongoDB.
So what we can do here is actually a read only property.
So let's see.
Let's let's do this
string, let's call it connection string.
Okay, we're going to remove the set, we don't need to any sailors in there, this gate, we're going to open up.
So that becomes a read only property here, we can just return the calculated connection string. So we need to do a little bit of string interpolation here. So what I can say that they connect normally, a MongoDB connection string looks like this. So
MongoDB,
lashes lash, and then here comes a host, and then comes abort.
So those are going to get from the properties, host and port, the properties that we declared over there.
So with these, we have an easy way to grab the connection string, as long as we have populated host and port. Now that we have this, it is time to perform the registration of that a MongoDB, a client, a Mongo client, that we injected into the MongoDB, it was 30. So this client here
has to be registered somewhere, right? And as of right now, what we know is that we do that stuff in startup. So just open up startup.
And here, let's go to configure services. That's what you register all your all your services, we're going to do this services at Singleton because we only want one copy of the Mongo, the IMO client for the entire service.
I'm Mongo client. We made it important namespace here. Yep.
And then here, instead of just declaring the type of the explicit type of the dependency to inject, as we did over here, in this other thing, you don't, what we're going to do is actually construct explicitly that type.
So that it is injected with the additional configuration we do this week, because we have to specify a connection string that that a client is going to need. So to do that we're going to do is say, Okay, so we're going to say service
service provider,
we're going to receive that service provider. And then we open up braces, going to add column there. And then over here, we can do things like First, let's actually grab the A, the the settings. So an instance of those settings that we have populated in app settings. Jason, let's grab them via our MongoDB settings class. How do we do that? So back in startup, it's two bar settings.
You can use your configuration property, the one that we have over here that has been populated by the runtime, you can use that one to say get section.
And then
so now you need to get a one of the sections in the opposite is the case and we name it MongoDB settings, which is the same name as our MongoDB settings, settings class. Therefore, what we can say is
just name off
MongoDB settings
sorry,
Let me type this properly DB settings. And then we need to import that namespace.
Alright, so that will get us a section. And then let's actually turn that an object which is returned as I configuration section, let's
turn it into a proper MongoDB settings
like this.
So now we have a setting subject. And now we can actually construct our Mongo client instance with
return new Mongo client. And then we will do settings that connection string, the property that we calculated in that class.
So we that we should be ready to register and inject the client into the repository.
Now that we did that, it is time to actually flip our service to start using our new MongoDB depository. So that we can do in this line, you remember, we previously registered the email it was it already. So switching to this other depository is as easy as you're saying here.
Mongo DB items repository.
That's all it is.
And then one more thing that we're going to do here just to make our lives a bit easier, is to tell the MongoDB, a client, the driver, how to serialize a couple of types in this case is going to be a the codes and the date time offsets. If you remember our entity item, it has both agreed on a date time offset. And I think our MongoDB is that if you don't tell it, how exactly we want to reflect this, these types in the database, they may end up with
a representation that's not very friendly, at least not for our learning purposes. So what I'm going to do is say, let's say these, so I'm going to do
these on serializer.
See if I can get the right namespace,
register serializer. Today, we're going to say new COVID
serializer.
And it's going to be
be some type.
Let's collapse this coordinate space, that string that tells delsey that anytime it sees a good in any of our entities, it should actually serialize them as a string in the database,
we're going to do something very similar with our date, time, date time offsets. So just copy that line. And I'm going to say, date time offset to your laser
based on type string.
And we'll see how these properties how the data actually looks like in the database in a moment. But for now, we should be ready to start testing this out. So I'm going to hit f5 here.
And I'm switching again to postman.
Here we're going to start logging in again. Because we have not implemented that we can start with a post.
So to do a post, just, I just opened a new tab, and I'm going to switch the verb to post. We need the request URL. So we can grab that from our previous proper use right here.
Should be that same URL localhost 5001 items. But what we're going to do, we're going to need in this case is a body, right, so the body the payload that we're going to send
to our API. So we'll switch to row here. And then we're going to switch here to Jason.
Let me minimize this a bit.
So here, we will just a type, the JSON that represents the entity that we want to create that JSON is going to be composed of a name, if you remember, it's just going to be a name
and
a price.
So trying to pick a name and a price. So for our first item in their database. Let's say we go for a
great x, and price is going to be let's say 22.
So that's all we need for a post. And then we're going to hit sent.
And as you can see, the item got created 201 created.
And we can see the response that we got from the API. We got an ID, how to generate it in MongoDB and a created date created via our controller.
We can also check out the heaters that we received here. And as soon as we returned a created at action. You can see how the location heater has been populated as
With the proper URL to retrieve the details about that item, but then also how does this item actually looks like in the database if you're curious. So how can we tell that. So let's go back to VS code. And let's stop this and close terminal, what we can do is install a MongoDB extension for Visual Studio code. So I'm going to open extensions hop over here.
And I'm going to type MongoDB,
in this case, is the first entry over there. So just click Install.
And with that, we have a way to talk to our MongoDB instance, I'm going to close this one. And if you see, there's a new item on the left side MongoDB when you click it,
and then there's a connection to already establish or defined there for localhost. 27, zero 17.
This may fail the first time you try to connect. So if it does fail, what you can do is just not going to do it, I'm going to remove this connection, or I'm going to add it again. Add connection, localhost 27, zero 17 is the default, and
close the STS and that, and then we're not, we're not using authentication at this point. So let's say no.
And then hit Connect. And you're going to see on the left side that you have a connection to your local instance of MongoDB
going to collapse these and these.
As you can see, there's a few databases here. And some of them are really default databases for MongoDB. The one that we cared about what those are closest, now that we care about is the catalog database that I'm going to open. As you can see, we already have both the catalog database and the items a collection over there.
We disband these. And you can see that we have one document for us open up that and these good here should match the good of the item created. So let's see FCO 27, open up postman is right here.
Sorry, the body of the response, FC 27. So that's the item back to VS code. And if you click over there, you can see the actual data that's stored in there. So as you can see, this is suspected as as of our document database, it is storing the data as Jason directly into the database, we have an ID, the name, price and degraded date. If we had not done these two lines here, these reducers realize it lines here, the data you would see either for ID and for created date would be in pretty much in a different format, that will not be very human friendly. But you may want to play with that decision a little bit depending on on your requirements.
I'm going to close this.
And I think it's time to implement a tournament winning auto route. Where are other methods in the depository, let's actually go for the get items route. First,
how to implement get items,
the only thing you have to do is this. But let's say return scope to our collection, I just collection, we're going to say find.
And here, since we want all the elements in that collection, we're going to just say new visa document.
Oops, there will be some document, I'm going to import namespace.
And then that will find all the documents. And then you're going to say to list.
So that will give us a list of all the items in the in the collection. So we found on respected a way to request all the items. But this is just one of the ways to do it. There's a few other ways. But yeah, this will get all the items in there. So with that, we can try to get items. So I'm going to do a five again.
Back to postman.
And happens do we already have the gate for is open here. So it should be as easy as running these again. So just going to click Send.
And as you can see, we do have the antidote from the previous video still hanging around, that's working just fine. Now let's try to create a brand new item, right, so how would we call it let's say this is going to be
high potion. So a potion that provides increasing strength to the player. And so the price is going to be, let's say 30.
And I'm going to hit send. Let's see if that works.
So we do have an issue here, we're getting a 500 internal server error, and no route matches the supplied values. So this here is actually unexpected situation. This is because of a breaking change in ASP. NET Core three. And let me let me show you why this happens. Let me go back to items controller, I'll use a stop this, close that. And if you remember, what we just did a in grid item async method is that we updated or created an action call here, to use a name of get items get async as opposed to get item because we just rename it that method over here, get item async. It was get it before. And now the breaking change that interested in ASP. NET Core three zero is that at runtime, the framework is going to actually remove the async suffix from from the mid range at runtime, this actually looks like just get it as opposed to get Iam async.
Given that when we try to do the greatest add action call here, it is not able to find the route that represented by that action. And so that breaks things. So there's a couple of ways to fix this. And what I'm going to do is to actually tell a dotnet that I don't want that behavior, I just want to keep using the async suffix. So to do that, what we can do is just go to startup
startup, what you want to do is find your call to our controllers. And there, you just have to specify a one option, which is options. Let's see options, open curly braces, and then you want to do options that suppress a theme suffix in actual names
equals false with that, that that will not remove the async suffix anymore from any method at runtime. So let's see how that goes.
like five,
back to postman.
And now the one thing that you want to keep in mind is that the creation actually succeeded. It just happens to be that we were not able to be a we were not able to create to invoke the created our action a call, right. So just to confirm that if you go back to get a return to do a get here, you will see that we do have the high potion created. So it is there. But we will not we were not able to complete the creation. I mean, we were able to call the Create action successfully.
So let's actually create something else here to not confuse these with a high portion. So let's call this one mega potion, or mega potion is going to be more expensive. Like let's say 45
So now I'm going to do sent.
And this time, they actually it was actually created successfully.
No issues there.
Now let's use the A this item to actually try the get route. So just to get a for that item, let's see if that works.
That works just fine. And now let's try our put our put route for that item. As so let's see, we're going to use the same name, perhaps make a potion. But let's put another price. Let's see what price we have. Here we have 45, let's say it's even more expensive 50 for the mega potion.
So I'll hit send.
And that should have updated the mega potion. So let's go back to the gate, make sure that the price changes. So hit send again. And as you can see, price is now 50.
And
let's see, let's actually not delete that makeup in we're going to try to delete route. But let's delete something else. Let's say we want to delete this high portion. Actually, he's kind of our favorite experiment. So I'll copy that ID. So I'll try to delete that.
Anil he'd sent two or four new content back to the target route. Let's see what we get. And as you can see, Hypatia is gone. So we only have the antidote and the mega portion.
You want to configure that things are actually getting read into the database. It's as easy as going back to VS code, and close. And I'll open up again, our MongoDB extension here, catalog items. And we do have let me refresh this. We do have two documents, one for the antidote.
And whilst they make a push, and yeah, now you have fully a synchronous
REST API, which is going to give you great performance and great efficiency. And from here on.
In this episode of The dotnet five REST API tutorial, we will talk about secret management and health checks. We will see how to securely store secrets during development that your REST API can use easily as with any other piece of configuration. We will also learn about health checks and how they are a great way to report the health of our API.
Today, you will learn how to store and use secrets via the dotnet secrets manager and how to use health checks to report the health of the race API and its dependencies.
So let's talk about secret management. As you know, at this point, we do have a REST API that is able to talk to a database or MongoDB database.
And in order to be able to talk to it, we have defined a configuration service where we have specified details like host and port. So to be able to connect to that MongoDB database. The configuration source that we've been using so far is our app settings, that JSON file right
there, we have host localhost and Port 27, zero 17.
But now we are going to enable authentication to the MongoDB database. So you're going to need a username and a password in order to be able to connect to it. We have to tell our REST API how to how to use this information. So for the user, we're going to add our user another user is setting in top settings JSON for are going to call it just MongoDB admin for the username. And then we also have to specify a password. So should we just specify the password directly in the app settings JSON file? Well, the answer is no. You don't want to add any sort of secrets into app settings JSON or into any of the files that you have that are part of your service. Right. So that's a basic, good practice in terms of security, never introduce secrets in there.
If we cannot do this, then how are we going to pass that information into the service. So it tends to be that
app service JSON is just one of the possible set of configuration services that can feed into your REST API. There are other options like you could use a command line arguments, you could use environment variables, or you could even use a bunch of other cloud searches, searches coming from the cloud that can provide configuration information into your REST API.
In our case, we're going to use one that's called the secret manager. This is just one more configuration source that's built in into dotnet. And that is already pre configured for you, for any new brand new web API are in that secret barrier. We can securely store our password, let's say pass in this case. And without having to put that password within our REST API. So it's not going to be in any of the files that compose our REST API. It will be in some placing our machine securely stored, but yet, they are REST API is not going to have any trouble reading that. That password because just as as anything else, it is coming just as a new piece of information.
majorly from our configuration service. And the rest the REST API can easy to consume.
We are now going to enable authenticated access to our Mongo database. There are a couple of ways to do this. But to keep things simple and safe, this is just a development database for learning purposes, we will delete the volume where Mongo container currently storing all the data. This allows us to start a new container with a brand new Docker volume with authentication will be enabled.
So the first thing that you may want to do is verify if you're running the container already or not. So we're going to do is open a new terminal.
And I'm just going to do docker ps, that will tell you if you have the container running already. And you are indeed, in this case, I am. So what if the first thing that I have to do is to stop this container. So I'm going to do Docker stop Mongo
that stops the container. And now what I want to know is, which is the volumes that I have available here. So Docker volume, LS, that will give us the one volume that we care about right now MongoDB data. And now what I'm going to do is just delete it. So Docker volume, RM MongoDB data.
So now the volume is gone, we are free to restart the container with a brand new volume. So what I'm going to do now is just a grab the initial set of a, the initial command to run the container, just as we did before. And then as you can see, the same volume is here, there's going to be a new one, since we deleted it already. And what I'd like to do is just add a couple of environment variables that represent the username and password that are going to be used at a within the database. So if I just do dash E, and this is the file, the name of the variable, in this case is going to be Mongo in it.
dB.
Root username.
And here we specify the name for our user, you could use any name here, I'm going to, I'm going to use Mongo admin.
So that goes for username, and now the password
for the password, you have to use Mongo ini dB,
root, password,
equals, and then you got to pick a password. So I'll pick something here, not super strong, but still
something like these.
And finally, the name of the of the Docker image that we want to use in this case is Mongo. So with this, it's a hit Enter. And now we do have docker ps, we do have a database that has authentication enabled with a user of Mongo admin and a password of pass bout, we're one.
So at this point, our database requests authentication, but our service does not know about it yet. So what happens if we try to query date at this point, what I'm going to do is just hit f5, the service.
Okay, and then go into postman. And I'll just try to query for a few items and see what happens. So these are items query it sent.
And yes, command find failed command phi requires authentication, right? So just good. authentication is working. It's just that our service does not know the correct credentials to talk to the database. So how can we make a how can we make this service a aware of the user and password needs to use. So let's go back to VS code. Let's stop and close this. And what I'm going to do is, first I'm going to declare a a configuration setting for the user. So I'm opening up series Jason. And in this section where we have MongoDB settings, I'm just going to add a one more section here.
For for user, let's call it user.
And we know that the user we specify for the database is Mongo.
Now, we also need a password. And of course, I could specify a password right here. But that it's not a good idea. You should never specify a secret or confidential information in your app settings JSON file, that's a security kind of a security hole. So you should not do that. Instead, we're going to take advantage of the dotnet secret manager to store the password security as still we should be able to pull that secret into the service into the REST API without any trouble. So to do that, again, I'm going to open a terminal.
And here's what I'm going to do. First, let's initialize the secret memory for our project. And that we can do via dotnet user secrets
a bit
And if you take a quick peek into catalog CEUs blog, you're going to see that there's a new entity there. User secrets ID. So this represents a identifier of this secret configuration for this project.
From here on, we can start actually adding secrets to the for this project, and do a secret what you can do is.net user secrets
set. And here you have specify the name of the secret. Now for the name where we want to do is follow the convention of the settings,
the settings property that we have defined already, and yours a add to it in the in, in the in the format accepted by the net. So in the in our case, that means is specifying MongoDB settings, and then we do colon, and then we specified the actual a property that will represent the password, this case is going to be use password.
And after that, we specified the actual value, in this case for the password, and we know that the password is pass bound word one, or one.
Okay. So just like I did here, what I'm saying is, is that the, the, the name of the secret is going to start with MongoDB settings, which matches a MongoDB series right here. And password is one of the properties that is going to follow the other properties that we already have in the MongoDB settings class, and the actual values passed
out word one, hit Enter. And the secret has been add. What we need now is a way to read both username, the user and the password into a our service. To do that, we're going to go into our settings, settings class MongoDB settings.
Absolutely, let's close this.
Let's add a couple of new properties here. So let's add
string, user.
And string
password. So these are the two properties that will be populated at runtime by dotnet. into into our app.
Now,
the other one that we want to define is a modified connection string because now it is not going to be enough to provide us whole support. We have specify user and password. And the way that you do that for a MongoDB connection string is by saying
user colon
password. And then we'll say at host column port. So that's the same text that MongoDB is expecting from us. So just by doing that, let's try equating again and see what happens. So I'll do a five.
And I'll go to postman now. So let's see if we can query for information sent.
And yes, we don't get anything because remember, these are brand new database because we modified the volume, but we are getting a 200. Okay, oh, things seem to be a running just fine. And just to be completely sure, what we're going to do is do a post. And we'll switch into the post tab here and see if we can actually recreate this mega portion with price 45. And not just that, let me actually put a breakpoint over here. And to see how these values look at runtime. And Cynthia Singleton, I'll have to stop and restart our, our service. So let's hit f5 again.
So the first time that the connection is needed, we should hit this breakpoint. So go to postman. And I'll do send here.
And I suspected we have a breakpoint here. So as you can see, the user has been read from our app settings JSON file, and the password has been read from the secrets manager. So password is not at all reading, updating JSON is just coming from secrets manager. And
really that that that magic that takes that does running here is being driven by program CS when we do create default builder, that piece takes care of injecting the secrets barrier as one more configuration serves to our service. So you don't have to do anything special for that to happen. So if this happened, and we just let it keep going.
Go back to postman. And in fact, yeah, the mega portion has been created. If we create it for them. We'll see that it is right there.
So moving on to the next topic. Let's now start talking about health checks and how to enable them. So But first, let's learn about health checks.
So as we know, we do have a REST API at this point that is talking to a MongoDB database.
However, it is not uncommon to face issues as time goes by, right? So a REST API could go down for a variety of reasons. Or it could be a good intention, right? We may be redeploying these REST API to our, to our server to our cloud service or to our server wherever it is going. So even if temporarily, that REST API could go down.
And there will also be issues on the other side with talking to our database, right? So for any reason, that database, we may lose connection to the database, either temporarily or for a long time.
Or something could be going on, and we're really talking to that database, right? So communication issues could happen. So with this, we may start getting equations like, is our REST API alive? So is it alive? Ganga, we actually talk to this REST API. Right. So you may start wondering this, or you may start wondering, Well, can we reach a database? So is it there? Is that connection in a good state?
So but really, what you're asking here is the broader question, is it healthy? So is our race API healthy? Is it ready to receive quiz? Is it ready to do the job the right way?
And to answer that question, the right way to do it is to enable what we call health check endpoint. So so you don't have to guess. So we will have, you will have a natural endpoint that you can call is part of our universal API. And you can call to it and it should be able to tell you if the service is healthy or not. Right. And, of course, there will be a person or people interested in that information. So us as, as a developer, as an engineer, that increases service may want to query for that help endpoint as either services healthy. But really the most important case, the most useful and original a scenario is when you have an orchestrator system, that will be in charge of knowing when your service is ready to receive requests. So we will talk more about this in a few episodes in the future. But
having a health check endpoint is a key piece of any REST API that you have think about right away.
Now that we learned about health checks, let's see how we can enable them for our REST API. So the first thing that we have to do is to add the services for health checks and that we can do within startups. Yes. Before that, let's stop debugging and close terminal and open up startup CS. And I'm going to head into the Configure services method.
In this method, we're going to go all the way down.
And we're going to add just one line here. Services dot add health checks
in now, so that's the services and now we need to add the middleware for it. And that we have to do inside the US endpoints method inside Configure. So here, we're going to say, endpoints that map health checks. And then here, you get to pick the route that you want to use for your health endpoint. So in my case, I'm going to go for health. But feel free to choose something else, like you could do something like hc, or you could do health See, it's really up to you, what do you want to use, I'll go for those held.
And now I'm going to do a five. And let's try it out. So that should give us a very basic health check endpoint for service. So back to postman. And the way that you query for this health check is really very similar to what we do for the iTunes route. So we just have to open up in a new tab in postman for the get burb. And I'll go for my localhost colon 5001. And then you just add the URL to route our case is held. I'll hit send.
And here's you get a result, he says healthy.
So with that, you have a way to almost like being your service to see if it's in a healthy state. So this means that yet the services is up and running, and everything should be fine.
However, this is actually not super useful, because even if the service is up and running, it means nothing if the database is down, right, if the service is down, or if we cannot reach it.
Our service is not really that much healthy.
So how can we tell if we have any dependent service like a database in a not in a healthy state? And how what how can we take advantage of health checks for this? So let me show you what we can do.
Going back here, stop that close these are going to do is add a nougat package.
That's called ASP. NET Core healthchecks MongoDB. That will allow us to add health check is specifically designed to verify if MongoDB is running properly. So I'm just going to open a new terminal here. And I'm going to say dotnet, add package, ASP. NET Core health checks MongoDB.
So this is an an open source project, it's not part of ASP. NET Core or dotnet. Five, but it is a very handy project. And with these with this package, the let me show you what we can do.
Close the terminal again. And then.
So here, what we want to do is add some options to this call to add a health checks. But before we can do that, we need to pull out our MongoDB settings in such a way that we can reduce them later on. So to do that, I'm just going to grab this, this line here,
outside and into, let's say here,
and we will call these MongoDB settings.
So now those MongoDB settings can be used over here for our MongoDB client Singleton. But also for our purpose right now, after saying a uthealth checks, we're going to say
that at MongoDB. And here we will use MongoDB settings,
that connection string
to specify the connection string that needs to be used to connect to the MongoDB database, right or health check is going to be based on can we reach this database or not. We will also add a couple of things here, like a name used to define this specific health check. And we will just say MongoDB. And one more thing is a timeout.
This is because we don't want this healthcheck to take a long while to tell us if the database is is just down. Right. So in this case, we'll say timespan from seconds, let's give it three seconds. If after three seconds, we cannot connect to the database. But we'll consider that this has failed write the check has failed. So now that we did that, let's try it out. So I'll do f5.
And I'm going to actually remove this breakpoint.
And back into postman. So here, yeah, I'm going to say hello again sent. And it is healthy. And I suspected because our database is up and running. But if I go back into VS code into our terminal, and I use a stop our
MongoDB container. So let's see Docker stop Mongo that will is Docker container, which is equivalent to just stopping completely our database, right and the entire database services going down. So let's see what happens as I go back to postman. And I tried to check her health
will take like two seconds. And then yes, right here. So now health check is reporting as unhealthy. Which is great, because now anytime a database is down, we can easily tell that that as part of the health check that we're using right here.
Now there's one more thing that we can use to improve the scenario. And is the fact that we may want to have more than one endpoint to verify not just the fact that the services is up or not, but also if it is ready to receive requests or not. And the typical pattern here is specify both a ready endpoint and a live endpoint. So our ready endpoint is going to tell us if we are ready to well, if a service is ready to receive every incoming request, right, which in our case really means so is the database up and running Ready to go? Can we use it? While the live endpoint is just going to tell us if our service a justice service is up or not? Is it alive or not.
So to do this is these two endpoints, let's actually go back to VS code. I'll stop this again.
And back to startup.
First thing that we have to do is going back to our health check configuration, we have to assign an attack to our health check to let me show you what I mean. So actually put these things down a bit so that we can do the easier. So I'm going to open up a
One more line here. And I'll say tax.
So I'll create a little array here.
And here, I'll say ready. So here I am attaching a tag that I'm calling ready. That will help me a group, every single health check for which I want to apply are ready endpoint. So the endpoint that specifies if I'm ready to start receiving requests,
how do we use this. So now let's go back to our map health check section.
And I'll add a line here.
So now we know we need to specify in our two endpoints, one for ready and one for life. So let's go for the raven first. So instead of just having held here, I'm going to say okay, slash ready, it's going to be our ready, ready endpoint. And now has specify health check.
Health Check options, see for museum a space right there.
Okay, open up this. So at this point, we have to specify what we call the predicate for the predicate is the way that you can filter out which health checks you want to include in these in this endpoint. So remember that right now, we only have one really, our that one has been tagged with ready. That's one form of MongoDB. So in the case of the radium point, we want to include that MongoDB endpoint. So to do that, what you do is is this, you say check.
And then we'll say check tags contains.
And then the tag name ready
that you have already endpoint that will, will will only include those a health checks that have been tagged with ready.
And then we have to define another endpoint. So I'm going to actually copy, copy these,
these one is going to be just live.
Really, for the live case, we don't have to do much, we actually don't want to, we don't want to include any health check, we just want to say a kind of the response over pink rice. In this case, we're saying just false. By doing that, we are excluding every single health check, including the Bongo DB one, and so that it would just come back to us as long as as long as the REST API is alive as the service is alive. So that's the way that you can do the life. So ready, we'll make sure that the database is ready to serve requests, life is just going to make sure that our site, our service is up and running. So with this, let's run again. So I'll do f5.
And I'm back into postman. And this time, so let's, let's see. So I'll query for, let's see, health, life.
see what we get.
So he says healthy, is expected because the service is up and running.
Now I'll try ready.
see what we get.
So after about three seconds, we do get unhealthy. And this is because our database Docker container is still still down, right? as we did before. If I go back here,
and I go back to my terminal, let's say this one, and actually restarted the Docker container.
For Docker, Ron, Yep, sure, do it. That starts a container again, go back to postman. And he'll sent and I get held up because the database server and the database are they're up and running.
And usually that's good enough. So that's what you want to do to enable your your health checks. But if you happen to want a little bit of more information about
the health check that you have configured, you can actually customize the message that you're getting here. This gives us the basic thing healthy, but you can get more if you want to answer Let me show you how to do that.
So back in VS code, open close,
we're going to look for our map health checks function. The red one in particular is interesting. And what we can do is take advantage of what's called the response writer.
This is post writer you can use to specify how to render the message that you're getting as you're collecting the results of the health checks. So I'm going to say
async context.
Sorry for that
context report.
Let's do this and then open curly braces, and then here I'm going to collect the result of the of the checkout.
So for that, I'm going to say sold equals, I'm going to use the JSON serializer.
The one that comes with dotnet,
I'm going to serialize.
Now we're going to create an anonymous type here. So I'll just say new. And then we have to give it a shape. So this is the shape of what we're going to return back into postman, to the collar. So the first thing that I want to show is a status started, we can get from report
dot status, just to string.
And then we want to get the array of checks,
which should include our MongoDB check. So in this case, I'm going to say report entries, select.
And I'll say, so I project each entry into a new anonymous type.
And in this type, I want to show first the name,
which is going to come out of entry that key, then I'll get the status of this very specific check,
which is going to come from entry value status to string.
And then there could be an exception coming from the database. So let's actually capture that too.
Oops, sorry.
exception, is going to be empty that value that exception, but we may or may not have an exception, depending on the status of this check. So we're going to say is that if the exception is not No, we will get an entry value, exception message. But if we don't have any exception, if it is no, we will just say not.
Finally, one last detail could be interesting is the duration that will tell us how long it took to do this health check.
And three value, duration to string.
game, so to taste.
And now one more thing that we can do here is to format that output. So I'm going to say, context, that response that content type equals
media type names.
And using something
that application that Jason,
that will let us render as a nice JSON string, back in postman. And finally, I'll actually write this information out. So await context response. Right?
Right async
result
using one new spacer.
And that should do it. So now we have customized how that message should be rendered. So I'm going to do a five once again.
And back into postman. So let's see what happens when I tried my radio endpoint now, sort of hit send.
And now as you can see, the result is a bit nicer. So we do have a status of Lt. And then we can see the array of objects. In this case, we have MongoDB, with a healthy state nociception and a duration in there. And if we wanted to get the sample with an actual exception to this, you can just stop your local Docker container again.
stalker stop Mongo
container stopped. I'll try again back in postman sent.
And after three seconds, we should get Yeah. So now, as you can see, here, the entire health check is unhealthy. The specifically MongoDB is unhealthy. And yeah, the operation has been canceled. So that's what we're getting from, from dry dock to MongoDB.
Now one last thing that I wanted to show you is that there's actually a bunch of health checks already available for you. And so just like we did a MongoDB one, let me stop and close this. If you remember, we did
this here, we did the MongoDB. One, just like this, there's a bunch of other ones already available for you to try out, depending on the service that you're using. Let me point you to this page here, which is a page for the open source project that I mentioned that has these health checks. So over here in this GitHub project, you're going to see that so this will tell you everything about how to use these health checks. But I wanted to show you that there's already health checks built in for a bunch of providers, right, so SQL Server, MySQL, a bunch of things Cosmos TV, send sendgrid, a few Azure services, Amazon services, Google stuff, here's the one that we use MongoDB. And
so yeah, so there's
A bunch of providers already available. And there's even an here's a nougat package that you can use. And there's even support for let me show you the support for for a UI. I don't usually use this one. But if you wanted to show a very nice UI with all your health checks, with a breakdown for for each of the checks, you can actually enable these, these health check UI. Sorry. And yeah, and that will just stand up another endpoint in your in your service that you can go to, and get all these nice UI rendered. So that's something that you may want to try it out. I usually find there's some old ways that I quantify the help of my services, as we're going to see as we move forward with these videos. But this is another option that you can try.
In this episode of The dotnet five REST API tutorial, we will start our path towards getting our API deployed to our production environment. We will learn about the relational challenges involved in getting the API beats deployed outside of our developer box, how Docker can help us address these challenges, and how to turn our existing API into a Docker container.
Today, you will learn the challenges of deployment, how Docker works, and why you should use it,
how to turn the REST API into a Docker image, and how to run your REST API as a Docker container.
So the way that things are at this point, you know that we do have a REST API up and running in our local box. And there's also a MongoDB database running next to it as a Docker container.
But now we need to figure out how we're going to share this REST API with the world, right, either how to share the REST API with people inside of our local intranet, or how to share the REST API with the world perhaps in the public Internet. And that place where we're going to share things is what we're going to call the production environment. The production environment could be anything from some server running in your in your building, or your house. Or it could also be some server running software in the internet, right. But for all cases, this is the production environment, this is a place where people will be able to access the a REST API without having to get access to your local box, your developer box. And one of the first things to think about when we want to pick this, this server for production is the operating system. So we will need to figure out when to find
the right server with the correct version of the operating system that our REST API is able to support. So in the case of for REST API is not really that much of a big deal, because that five is cross platform. So what we have built so far is to be able to run in really in a bunch of operating systems. But still, we need to sell install operating systems. So if we say that we're going to be running this on on Linux, then we would need to go ahead and make sure that we have Linux available the correct distribution and version of Linux available in that production environment.
Then, we also know that we do need the dotnet five runtime to be able to run a REST API, the REST API is built on dotnet five, therefore, we do need the dotnet, five run time, and all the corresponding files of it, they need to be placed in the right place in that production box. On top of that, our app may have a bunch of dependencies, like the MongoDB driver that we've been using to talk to one MongoDB. So like that, it may have a bunch of dependencies that they also need to be placed into the production box. So that finally,
our REST API can be placed alongside all of these other components, and it can run happily. So it is at that point that we can, we can continue that we have all the files and sorry for the REST API to be running in production.
But then also, we have to consider that we also have database requirements, right. So we have a database that we need to play somewhere in this production environment. And it will have its own requirements in terms of operating system, and dependencies that MongoDB may have the MongoDB and Gene, all these things that we need to run the database in production. So that by itself presents another set of requirements. So as we think about all of these, these things that we need to that we need to happen to get our production up and running. So we need to think about a few challenges right. So the first thing is going to be preparing a box. So what you really want is to make sure that whatever box we use for production has everything that we that we have had so far in our developer box, right. And then but you also have to think about so are we going to pick a physical machine Are we going to pick a virtual machine or virtual machine is really just average the latest version of operating system running on top of some other physical machine. So any regardless if office
same physical machine virtual machine, we need to figure out well, where are we going to get this machine from? Where are we going to host it? Who is going to be taking care of this machine? A bunch of questions just regarding this this machine. But also way to figure out okay, so is it going to be Linux? Or windows? Who's going to put the right version of the always in there? Who's going to make sure that he has the right set of patches? And who's going to maintain these? Really, somebody has to take care of these things? But also, how are we going to take the files to the production machine, right? So we have all these files in their box? And somehow they need to land in production? How are we going to do this? If we're going to be using perhaps some FTP protocol, to be able to talk to an insanely fast to production machine? Are we going to put the files in some USB or pendrive? And then just copy them using it into the production machine? Are we going to send the files to some person from operations, B, perhaps some email and then the person will place the files in there? How do you make this happen? The first thing to get there? So one way or another?
What happens? I mean, in this case, we're thinking of placing, let's say that the database server just next to the REST API, if we're going to do that, how are we going to make sure that all the dependencies of the database and the dependencies of the REST API
have the right versions, even when we only were placing everything in the same machine? Right. So there could be version mismatch between the things that are needed in the MongoDB database? And the detail needed in the REST API? Right, starting from the previous system? How do we make sure that all the dependencies are the right version for both of them? And if they're, if they cannot be, we have to figure out okay, so perhaps we need to split his REST API into one machine and the database server into another machine, which could not be that uncommon. But we have to think about all these things. Also, what if we eventually decide to move to a new version of dotnet? Right, so let's say that net six is out. We have to we want to move to the next six. What does that mean? Do we have to bring in a brand new set of servers, physical rooftile, that are already enabled for dotnet? Six? Do we want to just update the version of dotnet in this existing server, and then somehow make sure that the app does not break by making that change? How are we going to make that happen?
Also, how do we start recipient the machine because it is not just about copying the files in there, right? So somehow, we need to start the dotnet app into the server, somebody has to do it right. So we have to bring in some sort of automation, sort of a scripting, something needs to happen in that machine, to be able to start with API and not just started but started fast. And we want to make sure that as soon as we put the beats in there, the application starts quickly so that it can start serving our users.
And finally, what if one instance of the app is not enough? So what if we start having so many users and us having one web server for a REST API is not enough? And now we need to bring in yet another? Let's say VM virtual machine for a recipe and then another one, and then another one? So who's going to take care of provisioning all these VMs? For us? What about the database? What if we alternate multiple copies of the database server to be able to handle the load? So how is all this going to happen? So do we need to think about all these challenges? Do we need to deal with all of this? Or is there a better way? So luckily, here's where Docker can help us.
So now, let's go back to our local box. So yeah, so we have our local box has REST API and the and the database currently, and we need to get to production. But now, instead of starting to worry about how to copy all the things into production, or how to make sure that production has the right things already in place,
we can start using this thing called a Docker file. So a local file is kind of a template of
all the things that are needed by your a, in this case, a REST API to get it deployed into production. So in the Docker file, you will declare things like the operating system that we're going to need. So you're going to say, I run in this specific version on this specific distribution of Linux, let's say, What is his version of Windows. So he is already declared in that file. And so you're saying as long as that version is available in the production machine, I am, I am going to run just fine. And not only that, you can say, Well, I actually need the version five zero of the dotnet, or ASP. NET Core runtime available in there. And
by doing this, as long as I have all the dependencies of the dotnet core runtime, I am able to run my my REST API, right, so you can declare the runtime that you're going to be running on.
You can also declare or you can prepare all the dependencies that are needed for your app.
Right, let the ongole be driver and any other DLL or any other dependencies that need to be present in there, you can specify how to place the files that you want to put in that in that production environment, you can say how we're exactly what to put them. And you can also tell it exactly how to get started or how to start that REST API. So that scraping that is needed to say how to start it, it can also be said in this Docker file. So just by using the Docker file, you get, you get, you're already handling a lot of the challenges that we were talking before, because this Docker file is clearly declaring exactly how the how the environment needs to be built for the REST API to run properly. And so but then, it is not not enough to use have this Docker file. So now that you have Docker file, what you're going to do is use this thing called the Docker engine to actually prepare what we call a Docker image. So what happens here is that the Docker engine, which is just a process running in your in your box, the Docker engine is going to take that Docker file, and it's going to tag it and and build what we call a Docker image.
Tagging is is really just kind of a synonym for creating a version for your image. So you're going to set a version on that image, and then you're going to build it. So building a Docker image means reading that Docker file line by line,
or executing all those instructions to prepare the environment where a REST API is going to run. And that goes all the way back to ensuring that all the exact dependencies are in place, put the files in the right location and starting the app. Right. So all of that is encapsulated in that Docker image. But then, once you have the Docker image, it is not enough to just have it running in your machine, right? what you really want is to make it available in production. So how do you take it from your box into production. So enter the Container Registry, the Container Registry is a place that could be anywhere going from some server in your again, in your internet, to somewhere in the cloud, it is the place where you can place your Docker image, you can push your Docker image
so that it eventually becomes available for a your production environment. So this Container Registry, I mean, you don't necessarily have to be the one that pushes the Docker images in there. There may be some some other images already available in the registry. So for instance, in the case of MongoDB, there, we've been using
an instance, the Docker image of MongoDB. Since a few episodes back, we did not actually create that image Damon was already there. And that image is actually available in a specific Container Registry called Docker Hub. So Docker Hub is a public registry, where many vendors place their Docker images for public consumption. But just like there's a Docker Hub, there's also other container, private cul de registries like a Azure Container Registry, or Google a registry. Amazon ECR, I think is the name. There's also these days, GitHub have a registry. So there's a bunch of versions of these available. But all of them are able to operate with your Docker images in the really the same way. So what you have Porsche Docker image into the Container Registry, then you're able to have your production box in the physical box, virtual machine, whatever it is, it is able to pull your Docker image into it. And by pulling it and executing that image, it turns into what we call a Docker container. So the Docker container becomes kind of living version, our executable or running version of your Docker image, but a Docker container. So
the Docker container just has it
has all the files and all the dependencies that have been declared in the Docker file, and executes the REST API the way that you have declared it in the Docker file, and just like what well, just like how we stand up Docker container for the REST API, we can stand out the Docker container for our MongoDB
Docker image. And then, of course, these containers can talk to each other on all of these magic, I mean, it only works just because you also have the Docker engine of the same Docker engine that you have in your box, you will have it available in a production environment. So
here, as long as you have the Docker engine available in whichever machine, you want to go ahead and run your Docker image, then your Docker image is guaranteed to be able to run as a Docker container in that environment don't need that you need is a Docker engine. And that brings a lot of benefits along the way. The other thing is that you not only just can just run one instance of your Docker container, you can actually run multiple instances of your local container. So as you need to scale up more and more, perhaps because you have too many users, then you can use to start spinning up more and more copies of that Docker image into Docker containers in production, without having to incur into a lot of hassles to be able to provision more and more environments. So, lots of benefits about
Docker really here, started with efficient use a resource usage. So as opposed to the case of having to stand up new virtual machines or new physical servers, you don't have to really enter into a lot of new resources. spinning up a new Docker image does not take a lot of RAM does not take a lot of this space, because there's a lot of caching happening in there by by this thing called layers in in Docker. So a lot of caching, memory is going to is going to only going to increase in terms of what, what exactly is needed by for your,
for your, for your image for your service, free REST API. And so you can really fit much more instances of your REST API, in this case, also have your database, many more instances in the same production box.
As you could before, just feed one instance of your REST API, or your or your MongoDB container,
or a MongoDB database, in the same production box, right, you can fit much more.
By using containers, there's also faster start, because
because Docker, the Docker engine is able to cache all these, these layers are only the very first time that it needs to pull down the Docker image, it will do that with all the layers apart from their own, it will be able to just pull only the layers that have changed. And so that allows it to really start very fast. So you don't have to also you don't have to boot up an entire operating system, just to put a your your app, right. So the operating system is already in place, you just need to start your app. And Docker is able to do that very, very fast.
There's also isolation if each of these containers is running in a in a completely isolated way. So it doesn't matter what's going on in the actual production host machine, or what's happening in any other containers running in the box. Each of the containers are running in isolation. And so from their point of view, they are the only thing happening at that point in time in that environment. So that that gives you a lot of benefits from that side.
And then also you can you can think of these containers have been able to run anywhere. And because like I said, as long as there's a Docker engine running in your production machine, you're guaranteed to be able to run your Docker container in there. So lots of portability. And finally, scalability, like I mentioned, in the same in the same space, where before, you will be able to run just one instance of your REST API, like in the case of virtual machine. Now you can actually run multiple instances of your local container with using much, much, much less amount of resources in there. So you can really scale significantly by using Docker containers.
Let's see now what we need to do to containerize or Docker eyes our REST API.
To create the Docker image for a REST API, the first thing we need to do is create the corresponding Docker file. However, before doing that, and to keep things simple, we will update our REST API so that it no longer performs HTTPS redirection, and allows the use of the HTTP address only. The use of HTTPS from here on is a topic out of the scope of this tutorial.
So what does this mean? If you remember, we do have two URLs configured for a REST API. And those are configured in our lunch settings file on their properties. And you said Lisa, Jason, application URL, we do have HTTPS localhost 5001 and HTTP localhost 5000.
And the way that things are configured right now, if anybody tries to access HTTP localhost 5000, they will get redirected into HTTPS localhost 5001.
You want to test this, what you can do is just launch the app. So I'll do f5 here.
And I'll open postman. And normally we would go to HTTPS localhost 5001. But I'll change this into HTTP localhost 5000.
And in this case, I'll actually open the postman console down here
to see what's happening behind the scenes. And I'll do sent to query for items. The query succeeded. But then if you see there are two calls in here, the first one for HTTP localhost 5000 slash items. It returned 307 code, which means a redirect. And then that was followed by a call to HTTPS localhost 5001 slash items. So that's a the redirect that has been configured right now is what we want to change for the Docker case, at least.
So how do we change this? Going back to VS code, and I'll just stop the app and close this for now.
This is configured in startup.cs.
In under the Configure method, we have this line here, up that use HTTPS redirection
So he turns to me that when you run inside the Docker file, well, sorry, inside the Docker container, the ASP net environment switches from development into production. And this is what we can use to put a conditional on this on this line. So we can say is, if, and that is development,
then we will allow the HTTPS redirection, but otherwise, we will not allow.
That's the only change that we're going to make here. We will see how this works. Actually, when we have the Docker container ready to go.
This point, I'll just close this. And now we actually want to generate or actually to create this Docker file. So there's two ways to create a Docker file, either you can create it manually, or you can generate it. For this tutorial, we will have to speed things up, we will generate. So you need a Docker file. Well, I would recommend if you're a business to recode, is to use a Docker extension for Visual Studio code. And that you can find if you go to the extensions hub,
you can just type Docker here.
And then you will go ahead and install the extension.
And now close this. And now what you can do is just say, view command palette, and then you can type Docker Docker files to workspace is the first option there.
Now you get to pick the platform of the Docker image we're going to create in our case will be dotnet. ASP. NET Core,
then you got to pick the operating system of the container, in our case is going to be Linux, just because it's the most popular option most of the time.
And then you get to pick the port that you're going to release in in in within that container. In our case, it's going to be Port 80.
And finally, if you want to generate a Docker compose file, we will not use this in this tutorial to hit No.
And now if you go to our explorer here, you're going to see there's a couple of new files, we have the Docker file and the Docker ignore file. So let's start by looking at the Docker file.
In the Docker file, each line that you can see here represents a one set of instructions that are going to be applied as the Docker image or Docker image is going to be built.
And each of these lines will also generate what we call a layer that represents the changes that are happening from one line to the next line. And that's what helps a lot in terms of a caching of steps as we do subsequent builds of this Docker image. So the first time we will build it, it will take time. And this was a good time. So silicon times is going to be much faster. Now let's go one by line by line here to understand what's going on.
So the first thing that's happening is we're saying that we will be building out our image base it on the dotnet ASP net image, specifically version five, zero, right. So that's, that's the way that you that you start by saying, Where do you want to go from by specifying dotnet. asp. net, you're guaranteed to be building a your image based on
working ASP net environment, in this case, a five zero environment that has all the dependencies that are needed to run an ASP. NET app. And not just that the ASP net image has a has a at the same time has been built based on the correct always based on wait where you're running your image on in our case, since we're going to be running it in a Linux machine. That will include all the dependencies to run an ASP NET Core app, or the net five app in Linux.
Now, we're also saying this is five zero as base. This means that this is going to be a our first stage of the building the container. This is
a good segue into the concept of multistage builds, which is what's enabled here. This means that there will be more than one stage on the build process. And in each stage, you can specify a different set of instructions that may have nothing to do with the instructions executed in some other stage. So for instance, here we're saying, so this is the first stage and we'll call it a base. And in this stage, we'll go from a dotnet. asp. net, which is the runtime image for ASP. NET five work there is going to be slash app means everything that happens after this will happen in the in the app directory. And then that we expose a port 80 this line actually means does not mean much is just a kind of a documentation field. The way that you expose a port is a bit different actually. But it is a there's a convention to specify the port that they are policing in. Now that temporarily finishes that first stage and in the next nine
In line five, we are going into our next stage that we're calling built. And the interesting
part about this stage is that it comes from a another base image, if you notice this is coming from dotnet, SDK
to dotnet. SDK five series are supposed to dotnet. asp. net. So SDK is the image that has all the build tools, and all the libraries, everything that's needed to build
and dotnet five app, which is not the saying that you need to just run other than five AP, right. So this, whatever is coming in this ACP image is potentially much bigger. There's a much, much more files, compilers and stuff in there that are needed for building the container, but not needed for just running. So that means that your final image that's going to go actually from the base image is potentially going to be much smaller than the image that we're going to use to build your container.
So we start with this stage, in this case, it worked there is going to be a slash src. So that's where we're going to place any files from here on. And that's exactly what we're doing. Then in the next line, we're saying, Okay, so let's copy the catalog, that CS profile, the one that defines our project into the root of the current location.
And they will run a dotnet restore, on that break. So that brings in all the nougat packages that are needed, then we say, OK, so we have restored all the packages, now copy every other a file that's needed a folder up. So that includes all the files that you can see on the left side, except for a few sections, but most of the files are included here. And we'll talk about that in a moment.
And then there's this line here, that's actually not that much needed. Because as you can see, it's pretty much the same as the as line six. So we will actually delete this in a moment. And then we go into the actual build process, where we say, hey, go ahead and perform a dotnet built on catalog that says Prague, we're modifying a Korean configuration to not be the bug anymore, we want to release version of the app to optimize it for the production environment. And the results of that build should go into the slash app slash build directory.
So currently, that finishes our build stage. So it's this section here. And then we're switching to yet another stage that we're calling it publish. In the bullish stage,
what we saying is, actually, we're going to go from the build stage. So notice, now we're going from build is the stage that we just created. And we will go from there and name it as bullish, and just execute the dotnet publish command. with similar set of parameters and the previous one, just changing the output directory. So that then polish, what it does is
it creates a new folder, in this case called Polish with all the files are needed in the right shape, to just execute the app. Now at this point, I'd like to point out that I find a few of these lines are a little bit redundant. So we could simplify things a little bit here. So I don't think we need to separate both a build and abolish stage. So I'm going to make a couple of changes here. So I'm going to actually remove these a warfare source line, because we already have these in line six. So I'm going to remove that. And I'm going to also remove dotnet build just because dotnet Polish also performs dotnet build for us. So there's no need for two separate lines.
And I'm actually going to completely remove the Polish stage because their build stage should be good enough for what we did here.
So now we just have one stage called built. One second stage called built, that will end up by running duct and polish. And all the files that we need should end up being in Polish.
Now in 912, we go back to our initial basic stage, build stage, and we're going to be calling it as final. Right. So now we're pretty much finishing what's what's going on here. So we switch again to the app directory. And then we're saying what we're saying is, okay, so now let's copy from the foolish build stage, we net, which in our case is no longer there, we actually opted for built. So I'm going to actually copy this and change it into build. So from the build stage, go ahead and copy whatever is in the app slash Polish directory, the one that will replace all the files, copy all of that into recording directory, which will be slash app.
And finally, we'll define the entry point for our app. So this is how we define how to start our REST API. And in our case, that will be just by hitting the we're executing the dotnet command with the catalog that dll file. That's all that's needed to start our, our REST API.
With this Docker file, we're pretty much ready to build our image. But before doing that, what I wanted to do is just show you about the dot Docker ignore file.
So this Docker ignore file, what it does is, it defines a series of files and directories that you want to exclude from the Docker image. So there may be a one or more files that you don't want to include in the Docker image, because it makes no sense to close. For instance, the stuff that we have on there that VS code is really only useful for development purposes. But it means he has, it makes no sense to include that into the Docker image. So for instance, this line here specifies Hey, use, go ahead and exclude everything under slash that VS code. And the same thing for a bunch of other files and directories. So a good file to not forget to keep in mind, otherwise, your image will end up being bigger than it is.
So with that, we should be ready to start building the array image. So what I'm going to do is just open a new terminal.
And to build the image, what you do is use a Docker is Eli.
So what we're going to say is Docker build,
then you, you have to specify attack, I mean the name for the image and attack for it. So that you do be the dash D, qualifier. And then we will give it a name, the name will be catalog, and then you give it a tag, which in our case, let's say is going to be v one. And finally you specify the directory from which you're going to execute the command. In our case, that's going to be
the current directory, which is specified by the.so. I'll hit Enter.
And that's going to go ahead and build the image. So first thing we need to do is go ahead and download any of them base images. In our case, that will be the dotnet ASP net image, the runtime image will go ahead and do that. And then for the second layer for the second build stage needs to pull in the dotnet SDK, a five zero SDK, a five zero image,
which may take a little bit more because it's speaker, and then it proceeds to perform the actual build process within the the image.
So Docker restore,
and they are to publish, and then all the other steps that ended up creating that image. So at this point, we have an image ready. And like I said, each of the lines going back to a local file, each of the lines here represents one layer that maps to each of the steps that you see here. So these are the layers that will be cached from here on,
so that you don't have to do them again. So in our case, if we don't have it, if you don't make any change, if you've tried to do Docker build dash D, the same command again, I hit Enter, really mostly nothing happens because everything is cached. So only if you make some changes, then those some of these layers may need to be executed again.
Now, one thing to remember is that this is not the only container that we need for our REST API. We also have the MongoDB container, which is the one that handles that access for us our database server, right and which is the one that has our database. So we need to make sure that our new container for the REST API is able to talk to the MongoDB container. And the way to do that, at least in the local box, is by setting up what we call a Docker network. And having both containers join that that Docker network. So how do we deal with this network to create a network, the only thing that you have to do is just say,
Docker, network, create, add use, give it a name. So we will say that five tutorial
and that crazy network
if you want to see the the existing networks, you can do Docker network LS.
And you will see we have the our net 530 network already created.
So now when we run our containers, we should take advantage of that network. So what I'm going to do is first make sure that I'm not running the MongoDB container yet. So I'll do docker ps, the MongoDB container is running right now. So I'm going to stop it
Docker stop Mongo
to that stopped, and I'm going to run once again, the same command that we've been using so far to run our container.
let me paste that here. Same command.
But I'm going to add one modify here that says network equals and the network name that you use was defined in this case, net five
tutorial.
So that makes a disk container a joint that did work. So I'll say Mongo,
the name of the Docker image, so hit enter.
And then the MongoDB container is running in that network. After doing that, we're ready to actually start running our a catalog container, which by the way, if used to Docker images, you should be able to see our created image. As you can see, we do have catalog be one,
ready to be executed, as well as the Docker images for the dotnet. SDK, and the dotnet, ASP net, runtime, and Mako.
So to run our Docker image, we're going to do something very similar to what we did with MongoDB. So we'll start by saying, Docker run.
And then instead of doing dash D, for the dash, we'll do dash ID for interactive, that allows us to keep our terminal connected to the Docker run process, just to see whatever whichever logs are coming out of that container. And then we will do the RM so that whenever we stopped the container, it actually it gets deleted, so we don't keep hanging around. And now we specify the port, similar as we did with MongoDB. Now we have to specify which is the port that's going to be mapped from our local machine into the container. In our case, let's pick ADHD, it doesn't have to be it, you can use any other body port. And that is mapped to the internal port in the Docker image. Now, for ASP net, and that five images. The base image itself, it's been a runtime image defines or overwrite the port where the app executes, at least for HTTP. So that port is 80. So regardless of the fact that we've been using Port 5000, for local development, when we base our Docker image in the ASP NET Core runtime image, the port is overrated into 80. So you will have to specify 80. And if you don't want to use 80, there are ways to specify the port that you want to use in your degree in your Docker file. But in our case, the Port 80 will be just fine.
Now we have to remember that in order for our app to connect to MongoDB, we have to specify a series of settings, right. So if you remember, when we run our container, we had to specify both the host to be able for it to be able to talk to our MongoDB container, and also the password for the user that connects to that to that database. So how do we specify environment variables in this case, it's fairly straightforward. We can use the M section, so you just type M. And then you have to declare key app name value pairs for each of the environment variables. The first of all we're going to declare is
the one for host. And then if you remember, in app settings JSON, which I'm going to open right now quickly, we have this hierarchy of settings, right we have MongoDB settings. And in what we said is we have host or user and password. So I'll go back to catalog jammal. And we have to follow that same hierarchy. So I'll do Mongo DB settings. And then the way to separate add to go into the hierarchy in in this java file will be be a double underscore. And remember when we run will be passing variables into the container, we use colon, one in this java file, the convention we're going to use is going to be double underscore. So that's How you address these different settings. And then we'll give it a value. The value for the host in coronaries is going to be MongoDB. service. And we'll see in a moment how we define this MongoDB service. But for now, so yes, this is how we can address the MongoDB container that we're going to declare later on. But for now, so yes, this is how we can address the MongoDB container that we're going to declare later on. Next, we need an environment variable for the password. But before we can declare it, we actually have to create this password, add ingredients. And luckily, coneys has this built in support for secrets. sensitive information like that. So in order to do that we're going to do is just open my terminal. And I'm going to actually use a shortcut here Ctrl J, that opens up the last terminal we used and scroll down a little bit. And then we're going to type here is the cube control an option to be able to create a secret. So that would be cube CTL. Create secret. And then I'll type generic. Generic is the type of circuit that we can use in this case, but there's a few other options that you that you can also use here, then it's fine for us. We'll name it, catalog secrets. And I'll use spooler, just because you can actually put multiple secrets in one of these secrets here. But we're only going to be using one. And in order to type it directly in the command line, what you do is say from literal. And then you have to specify the name and the value for the secret. So the name is going to be MongoDB. Password. and the value is going to be just like we did before as bad bound word one. And then I'll hit enter. So that created the secret. Now that we have created the secret, we can actually go ahead and declare it and feed it into an environment variable. To do that, we'll do something similar. So we'll say name. And again, we'll follow the the hierarchy, right, so MongoDB settings, in this case, double underscore is going to be password. And then in this case, we don't want to use the value, we want to read it from the secret. And to read it from the secret what we can say is value from Enter. And we'll say secret key ref. So we're referencing a secret that's named name is going to be catalog secrets is the name that we gave the secret. And the actual key within that catalog secrets is going to be Mongo DB. Password. So that's how you can address a secret from the declaration of an environment variable in Kubernetes. That's looking good. I'll close this terminal again, have more space here. And the next thing that we want to specify here is our our health probes. So the health probes is a mechanism that enables coronaries to constantly monitor the health of our race API, by using the health checks that we already defined. If you remember, a few episodes ago, we declared some health checks in the REST API. So now it's we're gonna we can actually, we can actually use them to let us know if our containers are in good shape or not. And he remember we had both liveness and readiness probe. So both of them we're going to be using here. So start with the liveness probe, and to declare it where you can use his liveness probe. And then the kind of probe that there's a few kinds of probe as well, what I'm going to be using here is the HTTP HTTP GET type of probe, which is just going to call some HTTP path with the get verb into the API.
And so the path that it needs to use is, if you remember, that would be held live. And it needs to use Port 80 for that one. And that's it, that will be the biggest probe. And I'll just copy this to define our readiness probe. readiness probe is going to be very similar, but it ends in red. So if you remember, we would use the liveness probe to tell if our recipient is up and running, and the readiness probe to tell if any dependencies of the REST API are responding correctly are ready and basically, REST API is ready to serve requests. So with that we have properly declared a deployment for our REST API. But it is not enough. So this will go ahead and create the bots pull this pulls the containers, and environment variables and all that. But still, there's no way to reach for anybody to reach into this container from the outside. And to enable that, we need to bring in another component that's called service. So that service, we could either create in another java file, or we can use security right here, which is what I'm going to do. And the way that you separate these two different resources in the Java file is by using three dashes. And then you can go ahead and start declaring your resource. So I'll do some IntelliSense. Again here, so I'll just type service. And that brings up the query service IntelliSense. Here, so I'll click on it. And that will scaffold a little bit of pieces in there that we can take advantage of. So the service is the is the type of resource that you use to be able to address resource within Kubernetes, either internally or externally, which is what we need at this point, the service we need to give, we also need to give it a name, just like we did with deployment. So we'll name it the catalog service. And then one piece that's not listed here that, but that is important for us is the type, there's a few types of services. But in our case, we want to use a tight named load balancer. Load Balancer is the one that allows queries to request natural IP, or a natural or not, I guess, not an IP, in this case, because it's localhost. But it requests a way for two to open kind of a window to the to the outside, so that people can actually reach out to our REST API from the outside. So we'll see how that that name resolves when we run this, but you know, various responsibilities. Otherwise, we cannot reach the service from the outside. For selector, we have to specify the port. Well, the label for the the ports that we have declared before, if you remember these ports, here, we declared the template, we said that the labels for all the ports that are going to be managed by the deployment is going to be Catholic. So that same label is the one that we have to use, what we declare the service, or the color selector with, we're going to say, well, we're going to this service is going to be able to target any of the ports that have the app label with the value catalog. So that's how you connect the service with the bots. And then you also have to specify, okay, so which is going to be the port, that that from the outside, people can reach into our API from the outside, and that has to be mapped into a port in the in the container. So that port in this case is going to be we're going to be using your ad just because it's the default port for HTTP. So that will allow our clients to not even have this Wi Fi port, it will just be able to use call directly into the API. So they will go into port 80. And the target port has to be the port that we have specified for the container to remember, over here, imports, we declare container port 80. So container port 80 is the target pool that we want to use here. So it's really mapping 80, to 80. But it doesn't have to be like that, we could, we could have said, let's say 8080 maps to 80, that will be totally fine, just like we did with the container. But it is more usual to use a port 80. So now we should be ready to start deploying these well, to deploy these bodies resources, both deployment and the service. So how do you deploy this into Kubernetes. So we have to go back to our cube control tool to be able to do that. So I'll open up my terminal again, Ctrl J. And I'll switch to my release directory. The command to apply this deployment is called this this java file is
cube control, apply dash F, and then the name of the file, hit Enter. And you should get a couple of messages stating that they got up the deployment has been created, and the service has been created. Now you want to see a which is a deployment or what's the state of a deployment that should be created. What you can do is to say cube control, get deployments and that will give you a list of all the created deployments this case is saying we have a one deployment named Gatling deployment and currently it is saying that zero of one of the bots is it is ready to go. So it means that it is not really ready yet. So, let's dive in a little bit more and get details about the actual bots got created. So what we can do is say, cube CTL get bought. And so as you can see all the with one pod that we have declared here, the name is starts with enable deployment. And then it gets a name for what we call the replica set, which we've not, we will not talk about in this video. And then finally, some identify for the actual pod. And then indeed, the port is not ready. So this is saying zero of one ports are ready to be used, right to be reached by the outside. But still, he says running. So this means what this really means is that our our liveness probe, that you remember, we have a liveness probe is is reporting a healthy status, but our readiness board is not reporting healthy status. So to find out a little bit more of what's going on here, and I'll expand this a little bit more. Let's actually get some logs from that catalog board. So to see cube control logs. And then the name of the bot hit Enter. And then we're seeing something interesting here. Let's let me scroll up a little bit. Yes. So here, as you can see, the our MongoDB readiness check is failing with status on healthy dubray him was cancelled. And as you can expect this is well, this is not expected because we have not really deployed any database yet. But this is great. This This confirms that our readiness health check, both have checks are actually working properly. And we just need to make sure that we fix this problem with the database, we bring it up. And then we should be able to have an REST API up and running. Let's close the terminal. And let's actually declare what we need to declare for our database. So I'll go back to migrate this directory. And I'll say new file. And this file is going to be called Mongo DB. Because everything we're going to declare here is just for the deployment of that MongoDB database. Now, the type of resource that we're going to create for MongoDB is actually called a stateful set. And see if we don't have a way to generate the skeleton for a stateful set. And yet, we're going to use the deployment template for this. So let's use to deployment select Windows deployment. And then what I'm going to do is just switch from deployment to stateful set. So let's say Phil said he has similarities with the deployment. But it is actually meant for stateful applications. So a stateful set provides guarantees about the ordering and uniqueness, uniqueness of pots. So when you see the pots that are created by a stateful set, they will not have random, random names, they will likely have some very specific order names. So for instance, in the case of this one, it may be called MongoDB, one MongoDB, two MongoDB, three. And more than that, if one of these ports dies, let's say MongoDB, one dies. And when it comes back, he will come back as MongoDB one once again. And this is very important in the case of MongoDB. Because we will attach a persistent volume to it, which will have the database data files. And we want to make sure that those files don't just get lost as as the port is reconstructed in the case that it needs to be given for any reason, right? So we want to keep it around. So the right type of resource to use for for a persistent service, like MongoDB would be a stateful set. So just like we did with deployment, one of the first things to do here is to assign a name to a stateful set. In this case, we will name it MongoDB stable set. And then one important thing that's not being generated here is what we call the service name.
And the service name is used to give a some identity both to the stateful set and to the bots there are going to be a managed by it, which is not actually needed for deployments. But for a stateful set. It's it's a requirement. The name that we're going to give here is MongoDB. service. We see how the service is died later on to the MongoDB service and back to the catalog later on. Then also just like we did with deployment, we need to define which are the labels that the status is going to be using at to select the ports that is going to be managing. So in this case, let's say that our bots are going to have the Mongo DB later the MongoDB value in its app label. And we're going to do that Then we have to make sure that in the pod template down here in labels app, we have to be using the same value. So all our parts are going to have an app label with a value of MongoDB. And then the stateful set is being configured to manage all those pods with the MongoDB value in the app label. Now we keep going down into the container section inside the spec. And then let's just give the name of the container listen this name it again MongoDB. And then for the image will be we'll use the same image that we've been using so far, is just Mongo, latest version of Mongo, then we keep going down into the resources section. And we will leave these resources, they should work just fine for us. But it's up to you to modify how much memory and CPU you expect to be using for your MongoDB server and the MongoDB database. Moving on, we have to declare the the port where the MongoDB container is listening on. And by now we know that this port is 27, zero 17. Right. So that's the port that we have to use to, to connect to that MongoDB service inside the container. And once again, like we did with, with gadelha yamo, we have to define a couple of environment variables to be able to talk to that well to be able to start up our MongoDB container. And those are the username and the password for the for the user configured for for MongoDB. So let's declare and sorry, let's see clear and M section. And here, let's start declaring. So the name of this environment variable is Mongo in it, db. root user name. and the value that we're going to assign to it is the one that we're using. So far more go admin. And then for the for the password, we're going to be using really the same secret that we're using gatok. So I'm switching to catalog here. And I'll just copy our password section into MongoDB. base here and fix it a datian. And then as you see we're reading from the same location, but the name of the environment variable is just a little bit different, is going to be Mongo in it, db. root, password, right, so that's a, that's a username and password environment variables that the MongoDB container is expecting. The next thing that we need to define in the case of the MongoDB container, is what we call the persistent volume, right. So we need to declare some storage way too, ask coordinators for some storage space to place the data files for our database. Otherwise, I mean, yes, a database will exist within the MongoDB container. But when the container and the port, the containers container is killed for any reason, the data will just disappear, right? So we don't want that to happen. So to do that, we create what we call a persistent volume, and greatest persistent volume, we're going to use what we call as a volume claim template. So let's do that. And they make sure I pick the right place for this. I should be here. So let's say volume claim templates. And really the balloon claim templates is a way to declare or to ask Kubernetes for some space, some storage space in the node, where the where the pod is going to be executed, right, so I need some space. And we have to declare a some details about that space that we're going to reclaim. So first, we're going to set up some metadata. And we pretty much just need the name here. So what's the name that we're going to give to these to this volume, that's going to be data.
And then we have to specify the spec specifications for the volume that we're going to ask for. So that goes into the spec section. And then here, we have to specify an access mode access mode. So access mode declares, which way is this persistent volume going to be going to be mounted into the note, and actually, this should go into a previous line to do it like this. And, for our case, the mode of READ WRITE, once, once it should be good enough, and really this one here, it means that the volume that's going to be created that's going to be mounted is going to be mounted as read write, and for a single note, so meaning that just one note can read and write to these volume at a time. If you wanted to. You wanted to have more than one node we able to write to this volume, you would have to use a different kind of access mode. And then finally, we have to define the resources that we need here. So in this case, we'll declare resources and Then requests. And finally, storage. And here's where we declare how much space we want for this volume. And for our case, let's go for one, I think the term will be gigabyte, similar to one gigabyte, that's going to be as much as storage, we're going to reclaim for this volume, say this. So with this, we have reclaimed space in the note, right? For this form for MongoDB. However, we still need to tell our container that such a space exists. And we have to map that space in the in the host machine to space into the container. And to do that, we're going to declare a volume mount, the balloon mount needs to be declared inside the container spec. And so I'll just type here, balloon mounts. And here, we're going to specify a name for it. And this name will have to match exactly the name that we have specified for the in the balloon claim template, right. So we said it's going to be data. So data is the name that we should use here. And then the mount pad is the part where traditionally MongoDB is stored its data files right inside the container. So that that space is going to be slash data slash dB. So what this really means is that when MongoDB MongoDB container writes into its data DB directory for the data files for the database, those files are going to end up into de a persistent volume that has been declared on the site here, right, so the data gets read in outside of the container and into the persistent storage in somewhere in the host. So this way, if the container goes down is the port goes down for any reason, the data is not is not lost, and they will come back as the port comes about with the same identity in the stateful set. The next thing that we need to do, just like we did with the catalog yamo is declare a service write a service that allows us to address or to get to the MongoDB service. And so I'll just please, and then I'll type service for cleaning service, so generates just like we did before. And just like before, we have to specify some name for the service, let's call it MongoDB. service. And then one important missing piece here is this thing called the cluster IP. So cluster IP is what you would use to specify the IP address for your service, right. And so by default, any service will get local IP that that can be used to talk to other services within the cluster. But in our case, we want to create what we call a headless service. So we don't want to assign an IP to the service. Because
in a stateful set, we want to address each of the nodes for each of the pots individually. So in this case, we're going to say none, so we don't want an IP in this case. So it turns it into a headless service. Now for the selector, once again, we have to specify how is this service going to be mapped into the ports that exist for for MongoDB. Right, so for that, we have to go back, once again into the bottom plate, the bottom plate metadata labels app, the label that we're looking for is AP, and the value is MongoDB. So we go back, copy that into this lector MongoDB. So anytime a request comes into this service, it will find the nodes that sorry, the ports that have been tagged with the MongoDB a value in the app label. And then that's what, that's how it's going to find them. Finally, we need to specify the ports. And so just like we did with the container, we're just going to do a simple mapping of 27 zero 17. So any request that comes into 27, zero 17 should go should be redirected to the port where the container is listening on. And that that port is again, as you can see here, 27 zero 17. I'll just copy that over here. Now that we have done this, it should be ready to to get to get started with this MongoDB container. So I'm going to open up my my terminal again, Ctrl J opens terminal. I'll scroll down this a little bit. And then I'm going to switch to the Kubernetes directory. And here I'll just do cube control, apply dash F and then our demo file MongoDB demo, and I'll hit enter. So both the stateful set and this service have been graded. And so at this point, let's see, how does the stateful set look like right, so let's do cube control. Get Data Sets. And they'll give us the one stateful set that has been created MongoDB stateful set, which seems to be ready, it's healthy, it's in good state, and it's ready to be used. Now that we did that, let's take a look at all the ports that we have right now. So once again, cube control get hot. And as you can see, now we have our catalog board, it is actually ready. So it's no longer reporting 01 is reporting one out of one. And that is because the ad because the the ready the readiness probe that was trying to reach into the, into our MongoDB database, it is now able to actually do it. And that's because we now do have the MongoDB service available. If you remember, when we were in catalog, Jamel, we declared that the MongoDB settings horse was going to be MongoDB service, it was not available yet. But now that we have declared it and we have deployed it, we do have the MongoDB service that I can reach out to. And then that allows our health check to pass so we can connect to the database successfully. I just like we have that board, we also have the port, the MongoDB stateful, set zero. So as you can see, it is not a random random ID as a as in the case of deployment is more of a very specific numbered and ordered index for these stateful sets. Right. So if it dies, it needs to come back with that same number. Let's now see if our REST API is working properly. Now that is running within coordinators. So let's open up postman. And last time that we did a get against our REST API, we did it over Port 8080. So because that's the port that we exposed when run it is as part of Docker. But now we have switched to Port 80. And we can either query the API like this, or we can just remove ad because it's a default HTTP port. So this should be good enough to do a get against the API. So let's see what we get. And sure enough, we don't get any results. Because remember, this is a brand new database that's now hosted inside Kubernetes. So this effectively don't have any data. But we can go ahead and create something in there. And so I'm switching to the post up here. And I'm going to also change these to just be HTTP localhost items. And then for body, let's come up with something quickly. Let's say we're going to create a potion again, price 12. And then hit send. And sure enough, the potion has been created, we can create a just one more thing, let's say an antidote.
With Price, let's say 17, hit send, and has been graded to go back to the get it sent. We are getting our two items now. So the REST API is working properly. But everything is now running within grenades. And so we talked about this capability of grenades to self heal, right. So it should be able to always enforce that desired state, regardless of what happens to the bots. So let's see how how that exactly works. So I'm back in VS code now. And what I'm going to do is just to get a little bit more space here to be able to visualize things better. So I'm going to move this left side all the way to the left. And then what I'll do is spin up another terminal with the split terminal burden. So that now we can have two terminals side by side. And I'll move things around a little bit like this, perhaps right there. And so what I'll do is on the left side, I'm going to be watching for the bots with cube control, get pods, dash W. So that lets me see any changes that happen across the the current existing bots in there. And on the right side, I'm going to simulate the killing of one of these bots. So let's say that I mean, let's imagine that the board had some bug, and that causes the bot to crash. Right that then what happens. So let's do cube control, delete board. And so let's try our catalog board or only catalog board. And I'll go ahead and delete it. So I'll hit Enter, and see on the left side, how right away. As soon as it starts terminating that port, it immediately starts spinning up a brand new container, right. So that's the capability of coordinators to always detect this discrepancy between the desired state and the actual state. And as soon as you detect the situation, it needs to do whatever it needs to be done to bring back things to consistency, right. So in this case, the Newport is up and running, and then I'll just do Ctrl C. And I'll do get pods again without the W and you can see that now. We have, again, our two bots. But now we have a catalog but with a different identity, right? You can see it's not the same identity as before. And that's because deployment type deployments, great bots are ephemeral, ephemeral in nature, right? So they are just stateless. Now, that's not the same case. As for our stateful sets, as we said, Our stateful sets should be persisting across the lifetime of a pod, right? So if we just have these MongoDB stateful, sets zero here, what happens if we deleted right? More than that what happens with the data that is being held by this MongoDB bot, because we already have data in there. So would it survive for the data to still be there, with the port come back, so let's try it out. So I'll do a wash again for the bots. And I'll copy the name of our stateful set. And so I'll do cube CTL, cube control, delete bot, and I'll do MongoDB stateful. Set, let's see what happens. And see on the left side, the pod is indeed getting terminated. But right away, Grenada is detects that it needs to bring it back. And it needs to bring it back with the same identity MongoDB safe was at zero, because this is a stateful set, right. And we don't want to lose the data that's being held by the sport. And in fact, if I just control CDs, and guys, I'll do get pods, we have our two pods. And now we should be able to verify that the data is not gone. So it should still be there. So if I go back to postman, and I query for the data, once again, we can see that the data is still there. So it has not gone anywhere. So that means that indeed, our persistent volume got created, and the data is being stored outside of the container and into the host. And that's enabled by our stateful set. So I'll go back to VS code now. And we also talked about this a capability of grenades to easily scale the ports and the containers inside them, without really much, much stronger, right. So if we are somewhere our network we need, we have much more requests in our website, we do have not just one port for catalog, but we need to have three, well, what would we do? And how can Kubernetes help us with that. So once again, let's monitor what's happening on the left side, get pods. And then what I'm going to do here is just ask Kubernetes to scale the deployment for discovery deployment, where you can do is
cube control, scale, deployments, and then it will be catalog deployment. And then you have to say how much right right now we have one. Now we want to have three. So hit enter. I noticed on the left side that immediately starts provisioning a bunch of new bots to enforce this new decided state, right, and this happens blazing fast, right. So if I just do Ctrl C, now on the left side and get bots, again, you can see that we already have three bots, and we only had one just seconds ago. So here you can tell, like the power of the combination of Docker containers, and Kubernetes, right, how we can bring in a much more instances of our containers of our of our REST API. In this case, without really much trouble just running one line, we now have three copies of the container running. And now, the whole point of having these data replicas of the pots is so that we can do some good load balancing between them, right. So when a request comes, it should land in one or another of these bots. And how can we verify that? Well, unfortunately, we don't have good means to do that right now. But I think we can easily introduce a little bit of logging into the bots so that we can easily tell which bot the request, the request is landing. And so to do that, what I'm going to do first is just I'll just close the terminals for now. And then I'll go to our Explorer, or go to our items controller. And here, I'm just going to add a little bit of logging. To add logging where we can do is bring in the standard a logger interface. So I'll do only a logger for items controller. Let's call it logger. So I logger is, is a standard object available in dotnet five, so pretty much in any application dotnet five. And so what you can do here, you just do dependency injection, as you do with any other service built in service of dotnet five. And I'll come up to the logger instance right here. Now that we have that, what we can do is just pick one of our API's and do some logging. So I'll go for the easiest one, which is going to be our Get diverse API. So in this case, I'll just open up here, one line. And I'm just going to log a little message that says how many items we found, right. So to do that, what I'll do is just logger dot log information. And then let's do a little bit of string interpolation here. First year I'm going to put here is the current current time. And I'll do that via the date time class, I'll choose the UTC time to string and then we'll show adjust the time in the format of hours, minutes and seconds. Okay, so type format there. And then I'll just do corner just say retrieved. Items that count items. Okay, so just for us to verify that things are landing in different parts. So I'll save these. And then they will not change. What we have to do now is to create a new version of our Docker image. So I'll open up our terminal now. And I'll delete one of these, so that we only have one. And here I'll do what we did in the last video. So I'll just run our Docker build command. And I'll be publishing this into Docker Hub. So I'll be I'll keep using the same format as before, using the username first, and then the name of the image. And then the one thing that I'm going to pop here is the deck because this is a new a new version of the image. So we should be bumping it, let's say to be two, this is necessary, so that coronaries can later tell that this is a new image, or that it needs to pull it down from Docker Hub, otherwise, he will not be able to do so then I'll say dot O, hit Enter. And then I'll just miss I'll just miss one parameter here, which is the dash D for the tag, you enter again, this is going to build the image, it's going to reuse some of the cache layers and then it's going to be just a piece that is needed. Okay, so the image is built. Now I'm going to do me to login into Docker Hub Docker login.
And now I should be able to just push the image shorter. So I'll do Docker push, would you see catalog, B to enter. And then again, some of the layers are going to already exist in Docker Hub. So only the layer that's missing, which is my little change of one line change here is the one that is included in this image and this one that needs to be pushed into Docker Hub. Okay, so with the image in Docker Hub, we should be able to tell Kubernetes that we want to start using it. So for that, I'll go back to catalog Jamel and I'll say hey, cornice, I don't want to keep using a demonstration one, I want to use version two. Save that. And then back in the terminal, I'll switch to our coordinates folder. And here, I'll just apply this file was once again. So cube control, apply dash F, and then we'll do capital gamma. Okay, and so life is if we do cube control, get pods, watch, see what's happening. So as you can see, the old the old container dope bots are getting destroyed. And new bots are getting immediately installed stand up for the new image, the new misperception that we need to be using. So this should take us a few seconds. And so I'll Ctrl C now. And I'll see the actual status of the pods. Let's see what we got. So yeah, so we have three new copies of our a catalog REST API, the replicas of the pod. And so now we should be able to tell if we're load balancing across them. So to do that we're going to do is again, I'll just make some more space here. I'll expand this terminal has even more. And I'll split the terminal now in three. So that we can add Delta locks of the of the three pots. So first here, now go back one directory. And as they could control get pots, so we get the names of all the pots, and then I'll do one by one. I'm going to do cube control logs for these one and then I'll do dash f so that we can tell the logs and then I'll do the same Here, which is our second part, let's take a look is going to be this one here, dash F. And then the third one is these guy here for our third terminal to control logs, dash F. Okay, so now we're tailing the three parts. And I'm going to go back to postman and perhaps we can accommodate this into here just on top of the other one to see what's going on. Yep. And, yeah, so one more thing, actually that we have to change is postman is our heater. So let me maximize this for a moment. Because the default behavior of postman is that it's going to send this a connection heater with a value of pickle keepalive. And what that's going to do is actually set up a persistent TCP connection between postman and our bot. And that will actually prevent our little exercise from allowing different subsequent connections to go into different bots. So just for testing purposes, and to see how things work, I'm going to disable that, that heater here and see how these things work. And, yeah, so with that, minimize these in this way. And let's send one request and see where it lands. You see, it landed in the first part on the left side, between two items at 704 52. Let's send again. And now we landed in in our second part, let's send the game loops. Again, in the second part, and then at this point is going to be a little bit random. I mean, the algorithm that's supposed to be using is round robin. But really, things can land in any pot. That is point. So there you go, load balancing in Kubernetes, fairly straightforward, without you having to do really much work on it. And if you scaled into 1000s of bots, then all of them will be serving your requests appropriately. In this episode of The dotnet five REST API tutorial, we talked about unit testing, test driven development, and how to implement them to raise the quality of our REST API.
Today, you will learn what is unit testing and why it is so important. What is test driven development, also known as DDD, and why you may want to consider it in your project, how to retest REST API controller via the x unit testing framework, how to mock dependencies via the mock framework, how to write better assertions via the fluent assertions library, and how to implement TDD in practice. So what is unit testing? This is a topic that I'm very passionate about. And to understand it, let me introduce a quick analogy. Imagine that you're a member of the team in charge of testing the SpaceX rocket for the first time, the engineers have used dozens, maybe hundreds of different parts and systems, some not even produced by SpaceX. To assemble this awesome vehicle. Everybody put their top game to build a rocket. And now we would like to see if everything worked as expected. launch day is here. And then yeah, that didn't go as expected, there has to be you can't just assemble a bunch of parts and test them all together the very first time the rocket is launched. Fortunately, this is not how they usually test a rocket before launching. Without getting too technical about rockets, because I'm definitely not an expert in the area. I just wanted to show here a simplified diagram of the parts of a rocket that I got from the NASA website. All of the different components and systems like the payload system, or the oxidizer are also individual units of this entire vehicle that all need to operate properly before the rocket can lift off from it. And the engineers don't just build this unit and send them to the assembly team to put them on a rocket and after everything is put together, figure out if all the parts work or not. Each part of the rocket is tested in isolation, likely several times, where before being sent to the final assembly into rocket. This gives certainty to the teams behind each part that as long as it is used according to specifications, that unit will work as expected. And the same goes for the team assembling the entire thing. They know that they can connect all the parts according to specification, and the rocket should lie. This saves time and money for everyone. Unlikely saves a few lives along the way. So in terms of software engineering, we can define the testing as a way to test every piece of code in isolation without external dependencies. Now coming back to our catalog, REST API. Even with a simpler screen at this point, we do have a few components that talk to each other, like the itis controller, or I didn't have auditory class, the Mongo client instance. And finally, the MongoDB database. Each of these components are made of a bunch of methods that represent the behavior that we can get out of them. For instance, the ages controller has has functions like get item, grade item, update item, delete item, and we will certainly keep adding more in the future. These are the different granular units that must individually work correctly, to ensure that the whole service provides the expected functionality. Therefore, for each of them, really to write a series of unit tests that really exercise every aspect and every corner or each of these methods to give us enough confidence on their quality way before trying out the whole service from postman or from any other client. Beyond this, unit testing has a bunch of benefits that you definitely don't want to miss. With a unit test, you can quickly verify your code without having to worry about dependencies. For instance, you can make sure that your class can retrieve items from the database without having to stand up or talk to a database server at all. And such a test can give you results in milliseconds as opposed to seconds or minutes, you can make changes without worrying about introducing regressions. After you have a unit test in place. You can refactor your code as much as needed without concerns of breaking the service. Because you know that the unit test will provide you with that safety net unit this will catch box at the point where it is easier and cheaper to fix them. Which is before merging your code to the code base on way before getting a deploy to production. fixing something that is already impacting impacting customers in prod can have an enormous cost, both in human hours and of course in the amount of money lost by anybody that use it or service. And unit test. If don't Well, can be the best augmentation of your REST API, since every use case should eventually turn into a unit test. And those tests can't lie. They must represent the way that the system works. Now that we know what unit tests are, let's also talk about test driven development or TDD. So what is TDD? Simply put, TDD is a software development approach, where you write a test before you write just enough production code to make the failing test pass.
This translates into a cycle made of three phases, a red phase where you write a test that represents your software requirement. This test fails because you have not implemented any production code yet. In fact, the test doesn't even compile at this point. A green phase where you write just enough production code to make the test pass. You don't need to implement anything beyond what's needed to pass a test. And inelegant or only code is allowed at this point. Finally, if needed, you refactor the code you just wrote while you keep running the test to make sure that they stay green. It is at this point where you eventually arrive to code optimize it for readability and maintainability. You keep repeating this cycle for any new piece of functionality. This is a basic cycle of DDD. Why would you want to embrace DDD? Well, there are a lot of benefits of embracing this practice. But there are three aspects or like most we did it, you start by focusing on the requirement, not on the implementation. This gives you a lot of freedom in terms of trying to properly address the requirements, because you're not constrained by an already implemented piece of code. When you implement the code, first, you end up writing tests that verify only as much of the implemented code as you have time or patience for because you already invested a lot of time and effort in that code. You can increase it this coverage, because by definition, you would have not written any more production code other than needed to pass a test. Again, when you don't do things the DDD way, you might end up with multiple corner cases that might feel that you might forget or might not have time to test properly reducing the test coverage. Finally, clean design is enforced from the start. As you write a test, you will naturally start designing the pieces of production code in such a way that leads to a passing test. The classes and methods emerge from the test, and you'll naturally avoid the pitfalls of writing code to Cobbold to be tested. There are three main unit testing framework in the dotnet ecosystem these days, and unit, Ms test, and x unit. They all fulfill the same purpose of allowing you to write and run your unit tests in an automated way. However, for any new project, I strongly recommend you choose x unit. This framework comes from one of the original authors of the popular end unit framework. But it was really meant to be more closely in line with the dotnet platform and to help write clearer tests. It is also more intuitive than ms test which requires more attributes in test classes. Some of them not straightforward to use properly, especially for developers new to the platform. Let's see now how to implement unit testing and TDD in practice. It's time to add a new test project for our unit tests. But before we can do that, I think we should restructure things a bit to give our REST API a more specific directory that will live side by side next to our upcoming test project. So I'll start by going to our explorer view on the left side, and I'll just look for an empty section over here. And right click, and I'll say New Folder, the new folder is going to be catalog that API. And now let's move most of the directories and files over there except for the VS code being catalog, API, and OBJ. So let's grab everything else into catalog API move. And now that has all the files for the catalog REST API. Now, let's close this. And then let's delete this directory, we don't need this bin directory or this OBJ directory, those are gone. Now just to match the folder name, let's rename the project name into catalog that API. And then we'll have to make a bunch of renames in a bunch of files, just to match this new project, right file name. So I'll copy this name here, close this, and then I'll go to search and replace. And we're going to replace catalog. That's us broke into catalog that API CS broke. So let's just replace all, they will do the same thing with the DLL. So gotta look that DLL, siapa DLL, it's gonna be renamed into candidate API, that DLL is replaced these all the files. Now let's look at the namespace. So today, we have this namespace catalog. And that should turn into base base catalog that API. And then for every everywhere classes that are using that namespace, let's make sure they use the new namespace. So using catalog should turn into using catalog that API to that replacement. Now let's look at our
to VS code. That's a JSON file, where we have these a workplace folder slash catalog API stand to change that, to replace these with slash gallery, that API slash how to using this file placeholder, all the entries. It's all done. And now let's go to lunch that Jason and do a very similar replacement, so workplace folder into catalog that API, or three folders slash catalog API, replace all this file. And I think that's what we have to do. So I'll just close these two. And then I'll do a Ctrl, Shift B to make sure everything is building properly. Yeah, looks like it is, or close these around this one here, you'll see that nougat packages are restored, but everything looks fine,
or close terminal.
Now among the things that we modified, and they'll collapse, this one is the Docker file. So the Docker file is now pointing to a catalog API that says broke. So I'll just, I just want to make sure that this is still building properly. So I'll rebuild our Docker image now. So open my terminal. Let's open a brand new partial terminal. And given this new directory structure, I'll have to go into catalog API. And here I can run my Docker build command again. So just to Docker build, dash D. And there's going to be Will you see slash catalog, B three. And so B three, because we were creating a new version of this Docker image, which was B two last time, hit enter. Okay, so the new image is created, I'll do Docker images. So it is right there, the image was created. So everything looks just fine. So this is great. I'll close terminal and close this file. So now we can actually create our display. So I will collect this for a moment. So we want to create a new display you could use at the same level. So go back to terminal, actually, and we're going to go up one directory. And to create this project, you do it very similarly to how you do for the creation of the web API. So you choose to just use a dotnet CLA. So dotnet new, and like I said before, they preferred this framework for this project is x unit. So I will go for x unit. Now let's name this project. catalog that unit tests hit Enter. And our unit display has been great, because this one over here it has a project file and an initial class, display class. Are there. Now one interesting thing that we have now here is that we need to build a not one, but two projects, right? And so anytime we want to make sure that everything is really properly, however, our physical environment has not been configured for that yet. So if you look at the JSON alkalosis, it is configured to be only the catalog API CS Pro. So how can we make it so that anytime we build it builds both projects. So there's a handy way to do this, that I'll show you. And what I'll do is I'll create a new file at the root here, just add a route that's going to be named, we'll call it yours, we'll get proud. Okay, and then I'll collapse this for a moment. And this file is going to allow us to build all the projects in one shot. So how do you how do you do? So let's declare the following. So you will do Raju SDK. And here we're going to be using the build trails traversal SDK. So to do that, you just type here, Microsoft dot build that traversal. And this was the file operation. So because he's actually going to pull a nougat package down into your machine. The last version that I found last time was 303. So I'll do that. Okay, let's go ahead and also, close this. And inside this section, you have to define an item group. So I can group, close that group. And here, you have to reference all the project files that you want to compile. So for that, you want to type like reference, include equals, and then we'll just do this, this expression. So everything, so star, that star, so any files that end in Brock, are going to be compiled by this file. Okay, so with this file, ready, let's go back to Explorer. let's actually go ahead and into task Jason. And let's ask it to no longer just build catalog API CS, but instead is going to build
build that block. All right. So with that done, I'll do Ctrl Shift v.
Yep. And now it's close to East. Let's close that. You can see that boat. Let me just do this for a moment. catalog that API DLL and Catherine, etc, they'll have been built by these one comment. So notice that you don't need a Visual Studio solution for these at all. This is this is my preferred way of building like all the products in solution in a solution. So build Prague use include all the break files, and that will do the trick. Right, so close these two now. And then our test break here, we'll need Of course, a reference to our API, because we're going to be testing the API controller. So let's make sure we have that reference. So open terminal again, I'll switch to my PowerShell terminal. And then let's see, I'll go into catalog that API service catalog does unit tests. And then I'll do dotnet, add reference. And then we're going to go into catalog that API. catalog API. That's Yes, bro. Right. So that adds the reference, we look at unit tests, close these, it will have that reference right here. Okay, so now the break the test probably can't use any of the files, or reference any of the files in their API project was that and then we're going to need a couple of additional nougat packages in this test project. Let's actually open these again. And these are going to be a first we're going to need the nougat package for a extension plugin abstraction. So let's do that. dotnet add Bakhash Microsoft extensions that logging abstractions. And this is because we are going to be using or trying to test our controller class, which, if you look at it quickly, the controller class does receive a logger in constructor over here. So we're going to be needing to use these a logger class. And for that, we need that to get package that we just added. The other package is the net package is called Mach. Mo Q. And this is this little framework that can help you actually mock your your classes, the classes you're using in your controller. So So that you can a test only the pieces that you care about in controller, but you don't have to worry about how to create or how or how the dependent or external dependencies of that class a work. And we're going to go into those details in a moment. But yeah, those are the two nougat package that we need for now. So I'll close terminal again. And then let's start focusing on our desk class, this case, you need this one. Let's rename this class into a more appropriate name. So rename these into items, controller tests, right, so the convention that we're following here is that if the controller is named itis controller, that's a class name, we're going to be using the class name with the suffix of tests for this class is controller tests. That's the one that we have here. And that's the one that we're going to use for the, for the class over here, titled The third test. I'm going to collapse this Navigation Pane now I'll do a Ctrl, Shift V, make sure everything is building properly. And then, if you happen to be getting any of these red squiggles here, we should not be there. But if it happens, let me just close these, what you can do is use to Ctrl Shift P, and do only sharp is dark, only sharp, or you can type that there on the sharp, we start on the sharp, and that should take care of that. Now notice this fact attribute that was added to our audio narrated this one method here. So fact is the attribute that you have to use to declare that one of the methods in this class is actually this method. So that's the only way for the test runner to recognize that it has to execute these tests. So use Don't forget to add fact to each of your test methods. There are other ways to declare or to decorate your metals assessments. But for this tutorial, we'll stick with fact. Now let's go ahead and start writing our first unit test. But before that, let's look again, other items controller class. And let's see what should be unit tested. We will write unit test for all of our, all of our methods are here. But let's start with get it basic, just because there's a couple of simple and nice cases that we can go across these these methods.
In fact, after receiving an ad, as you remember, we will reboot and we will try to find the item in the repository. And if we can, if we cannot find it, if it is no, we will return not found. Otherwise, we will go ahead and convert the item into a detail. So let's write a couple of unit tests for to verify this method. So going back to our test class, one thing that I'd like to encourage you is to use is to use a good naming convention for the unit test. This is very important because it helps it really helps to identify what is this they're supposed to do what what is it really verifying, without having to go into the all the details about the unit test? So one one good naming convention that I like is the following. You want to use three parts here, that's going to be unit of work. State under test, and expected behavior. Right. So first part you into the work. So what is that you're testing? What is the function that is going to be tested by this unit test? That's unit work, then state under test. So under which conditions? Are you testing these these test methods, right? And then finally, the expected behavior. And so what do we expect from this from this unit, as we execute these tests after we execute the action part of these tests. So now translating that into our guided async method, let me just copy the name here. The unit of work is in fact, the name of this method. So gate item async. And the first case that I'm going to test is what happens when we cannot find the item. So the item, the item is full, right this section here. So in that for that case, I'm going to say with an existing item, so the item does not exist, what will be the expected behavior, then it's returned returns not found. Okay, so that should be a good name for arguing this refers to a test. Now, within the body of the reading test, there's also some other another good convention that encourages to encourage us to write the test in this way. So there's going to be three sections here. The first one is called a range. Second one is called tact. And the last one is called cert. These also a be named as AAA. Arrange, act assert. And the idea is that you're going to first have an array section where you're going to be much set up everything to be ready to execute the test. So just before securing the test, so this includes any sorts of marks. levels and inputs that you're going to need to be able to figure into this. Then we go to the x section where we actually execute the test, we perform the one action that we are testing here. And finally, the assert section where we verify whatever needs to be verified about the execution of of the action of the unit. Okay, so how does that translate into a unit test? So we want to test is controller. And for that, we'll have to of course, create an instance of item controller. However, remember that we need two parameters here. So we need to have a positivity and we need a lager. Now, one thing to remember here is that for the purpose of testing this unit, this get imazing method, we don't really care about the internals of how the repository for instance, accusatory behaves, so we don't care what happens. Normally, when you call get it basic, as you know, that we'll go ahead and talk to the database, retrieve the item and all those things. But we don't want to test that we don't want to test a repository, we just want to test the get itemizing method, this is the one unit that we want to test. So you have to make sure that you find a way to exclude those dependencies and the behavior of the dependencies from these tests, and just focus on testing what was right here. So to do that, of course, we cannot use a real a real Idaho story or a real logger. And here's what we have to introduce a what we call a, what we call a stops. And so I stopped is going to be a kind of a fake instance, or a fake version of these items of these classes that are going to be used just for the purposes of the test. So let me show you how that how that looks like. For this case, I'm going to say so we need a depository, right. So let's I'll say depository,
stop equals new mock. And then mark is a con for the from the mock library that we installed a moment ago, using mock. So this is a library that allows us to mock any of the mock or stop any of the different dependencies of our class. So locally, our controller, as we looked at the previous videos, we made it in such a way that we can do dependency injection in there. So the controller really doesn't know what kind of repository is coming in here, or the kind of logger comes here, you just look, it's just looking at interfaces, as you can see. So that gives us the flexibility to fake these both dependencies from or from the unit test. So I'll go ahead and say okay, so this is going to be a new mock of items repository. Okay, let's see, if I'm using a meeting any space. Notice that I'm naming this as a stop and not as a mock. And that's a slight difference that I like to make, because when you do stops, you will not verify anything on the on the on this object itself. When you do mocks, you will in the assert section, you would go ahead and actually verify something that happened to the mock accuracy test. So it's a slight difference, but it is good to it's good to to do the proper naming here so that you understand what is the purpose of the variable that you're using. Okay, so now the expectation is that when we call this call to controller, when we're going back to our method, when we call the get imazing method, the idea is that it will return a no. So when returns No, we should return the controller should return not found. So we need to set up that isn't it? So how do we set up this method in the mock so that it returns No. Going back here, what you can do is this repository, stop that setup. And then you're going to say, let's say repo, repo that gate item async. And then comes the parameter. Now, at this point, it really doesn't matter what's what's the item that we are going to be passing in here. It's irrelevant, because what we want to do it revelate is what happens with the return value. So because of that, what you can do is just say it is a which is a function provided by Mark also any COVID This means whichever value comes in, it doesn't matter. So Mark will take care of providing some value there. And then that will do it should return a value of No, that's what we want. That's the expected behavior. And then, but just so that marketers doesn't get confused, we have to cast this into the item entity. And let me add Robert namespace right there. Okay, so this is setting up the scenario for artists. So we're saying again, we're saying when whenever the controller invokes get good at basic with any any code, which mark is going to provide, you have to return a null value. That's what we need for this use case. And then similarly, we'll do something With the logger, so we'll say logger stop equals new mark. I'll say, Hi logger, or items. I just control. Okay, let's see if we're missing something again. Yep. See dad remains in the construction space. Okay, so now we have two stops ready to go. And now we need to create the actual controller. So now we can say, controller equals new items controller. And then we passed it, this docks. So if I posit Ori, stuff, what we need to get actually the object property of these because that's the real object that's going to be passing out just a mock the mock object, and then logger stop that object. So that covers the arrange phase of this unit test for the act phase. Here's where we execute the action. And this is normally just one line where you execute what you're going to be testing. So I'll just say, sold, equals await. And yeah, now that I'm using a Wait, let me remember that we have to use a proper return type for these unit tests, since we're going to be calling an async function. These days, it should actually switch to be an async. task. C. So now we can do a weight controller, that good idea may sync. And then, like I said, it doesn't matter what what good we provide here. So you'll say, cool, great.
And that's the action. And then finally, we'll go ahead and do the assert. So we next unit, you have a bunch of assert methods available. So let's just use assert. And then what we want to verify here is that what we got was indeed are not found, and they're not found, there's a class that is not found. So what we can say is assert that is type not found. result. And then let's see if we're missing something. Yeah, add that. And then we're going to pass here is result that result. So the result object, the result variable represents the actual result that we received. And the result property inside that result represent the actual type of result that we got, which in our case, it should be not found result. And now that we have that, we can go ahead and actually execute the test. And so there's a bunch of ways to actually run this test. In VS code. One of the ways that I use frequently is a via via using the kotlin. So as you can see, over here, there's these these set of annotations, which is, which is introduced by kotlin is to code kotlin. And you have to Will you have to do is click on Run test. And that should go ahead and run the test. Let's see what happens to running test. And that this has passed, as you can see, the one that we have passed, which is good. Now, there's another way to run the tests by using directly the dotnet. CLI. So if you go to terminal, I'll switch to here, make sure that you are in the catalog unit test directory. And here, what you can do is just do dotnet test. Yeah, so that will go ahead and run not just not just one test, but all the tests that you have across the desk like so difficult, more handy when you have you just are having more and more tests for this test project. And as you can see, it passed zero failed, one passed. Now, as the number of tests increases, you may want to have a better way to visualize the overall status of your test suite, which is cases are passing which words are failing, and so on. So So to do that, there's actually a nice Visual Studio Code extension that you can install to provide the dissertation. So let's go to our extensions hub. And let's expand this a bit. And let's look for dotnet core, this Explorer, just the first one here, I'll collapse this for a moment that will cortex explorer by June Han is the best one that I found so far for these kinds of tests. So go ahead and install it. And what you need to do about this extension is to tell it where to find the the test project. And if we go to Explorer for a moment, you'll remember that we have artists in this category test folder, gather the unit test OCS proc, so we need to provide that location to this extension. To do that, you can click this gear icon over here. Just click on that extension settings. And if any chance these settings that you see over here are not showing up for you where you can Try is just closing close to the code or closing the current folder and reopening it. And then these things should show up, it may happen the very, very first time, but after that, it should be just fine. Then what you want to do is go all the way down where it says Internet Explorer display bad. These are windows specify the path of this project. But also, do you want to go not into the into the user section, but into the work workspace section. By doing that you are going to what you're going to type in here is going to live alongside your project, as opposed to so in some place in the in your user profile. So that's good, so that you can keep everything, everything together. So here, what we want to specify is just a simple expression. So let me type that is a glob pattern where we're going to say just search all over the place inside of our catalog directory, and in all directories, and then look for tests that CS Brock, okay. And after doing that, we can close this. And if we go to these three dots here, there's now a test section. And as you can see, there's our one test is already showing up over there. And then to run it, you can either click display icon here, or you can just click the play icon on the top, and it will go ahead and run all the tests that are available there.
So aspect that is green. And you can see the green bar also in on top of the test. And if there was any error that you will get some red squiggles in the location where we will test failed. So yeah, so we're using this extension across this video to to see the status of our tests as we are adding them and executing them. Okay, so now that we have that in place, and we have a test for checking for the existing item case, let's add another test for the existing items. So what happens when the item actually exists. So let me actually collapse this for a moment. So I'll do hide sidebar. And let's add a second test here. Perhaps I'll just copy the header of this test. So I'll copy that over here. Here's a new this method. And this one is going to be called get item async. Wait, existing item. returns. expected it. Okay, now, just like before, we will do the AAA. Arrange, act. And assert now for these tests is very likely that we're going to need a again, our our capacity and our logger stop boat stops. So instead of copying, then copy the instantiation over here, why not just declare a class level a couple of class level fields. And that way we can reuse them in this test and in any future test. So let me do that. So I'll go here. So I'll declare by bait read only to be marked off items repository, then I'll just copy this base here, although I could just do this. And then private with only mark of a logger, or items controller. And then it will be the loggers stuff we can use to this. And with that, we can go to our initial test case, and to simplify a little bit by not having to declare it here, because we stop there or they stop here. Okay, so going back to the new test case, for this test case, we will actually need to have an item that we can use across the desk. Because let's go back to the controller quickly, I this controller IDs. And if remember, get item async. In the case that we want to hit, which is the one that returns the DTO we need to have an item, we need to have the repository, get the return an item. And then we need to convert it to and return it. So to do that, we'll have to set up that item beforehand in our test so that it can be used it over here. So I'll go back to the test. And instead of just creating it on the fly for this test case, I think that we should have some sort of helper function that we can use not just in this case, but instead of user tests to create some random item very quickly. So I'm going to go ahead and create a private function to private identify to return an item. Let's call it create an item and this is going to say just return new And let's specify all the properties for the new item. So it will be ID, this make it good that you go in, because we don't really care what Id it uses the same way we don't really care what name these random item uses, should work with any any name. So new gu ID, to string, them for the price we want to do is probably just generate some random number so that we don't get fixed into any specific price. So for whatever, what I'll do is I'll actually create a random a variable, and let's put it at the top over here. So I'm going to declare a private read only. Random and let's name it yours around, it's going to be new. So this will be with us in a couple of places. So now we can go back here. And we can say ran that next. And then I think we said that the price should be between one and 1000. And so let's just say a maximum value of 1000. should be enough. And Rarity is going to be the time of said that UTC. Okay,
so now that we have this handy method, let's go back to our test case, over here. And in terms of arrangement, what we have to do is first prepare the item that we're going to deposit or is going to return. So we're going to say bar spec, this item is create random item, then we have to do the setup for the repository. And that's going to be a little bit similar to what we did the previous test. So I'm going to copy this, this first line from the previous test there. So when we call get item async, again, with any good on what to do now is return that item. So returns async. Expected item. Okay, and then we'll go ahead and do pretty much the same thing that we did in the previous day. So copy this, these couple of lines over here. So we declared a controller with the two stops, and then we get a result, right by invoking get item async with any GUI doesn't matter. And then it is time to assert a what we got. So what would we like to assert here, so probably want to first make sure that we got the activity or not some other a result, like not found or bad requests or something like that. So I'll do a search that is type identity Oh. And let's add in a museum a space. And then we're looking at result dot value because it is the value property, the one that actually should have the deal in this case. Okay. And when we have asserted that well we can also do is, what we should do is verify that all the properties of the return to match the expected item. So for that, let's first take out that DTO. So we can do that by doing some casting here. We sold as action result of identity to that value. Okay, we can do a ser equal expected item that Id should equals do that ID. And just like that, we will need to go through every single property right, so x with the ID now expected item. That name should equal the to that name. And, and well, we'll keep going and going with the other properties. But at this point, imagine that you don't have just a couple of properties, but you have dozens of properties, right as objects can can get complex. So today is going to be become very cumbersome to just keep searching and searching and searching. And in fact, it's not a very good practice to be asserting too many things. In this case, you should try to assert or get closer, just one thing in each test, that's kind of the best practice. And so to do that, what we can do is instead of doing all this, we can switch to a very handy assertion library that's called fluent assertions. And davon will allow us to do this in a much more straightforward way. So let's bring in the terminal as well. Ctrl J and l go to PowerShell and hoovering we do so make sure that you are in the catalog that unit tests directory, and that you can do dotnet add package. fluent assertions. Okay, so with that, I'll just close terminal. So now we can do something a little bit different. So let me show you. What we can do is now we can say we sold that value And then should, then it's important if your assertions namespace be equivalent to the expected item. Okay, and then I'll remove this. What this is expected to do is that it should compare the values of the properties of the resulting DTO. With the properties of the expected item, the item that we created over here, right? So that way with this very handy method, we don't have to go property by property, it will just go ahead and compare the entire thing for us. But that the only issue here is that seeing suspected it is actually a record type. As you remember, the item is a record type record types already. Alright, the equals method of the of the object, and that will make it so that this method doesn't behave very well, right, because he believes that you have to compare the DTO to the entity directly, as opposed to comparing the properties, which is what we care about. So to address this, we'll specify an additional option here.
Which is going to be Options, Options dot comparing by members of the item of the item class. Without we're saying, Hey, don't compare the DTR directly to the to the item, just focus on the properties that each of them hum and as each of them have. And as long as the property is the same name, compare the values of those properties. So that way, you will go ahead and and issue go ahead and tell us that the objects are the same. Yep. And so with that, let's go ahead and run this test case and see what we get. So we'll go ahead and click Run test. Yep. And this case is passing. And just like we did this, just to keep things consistent, let's also modify the previous test to also use fluent assertions. So in this case, is going to be result that result should be off date. Not Found result. that replaces the previous line. Okay, so let's just verify that all these cases are passing, I'll go ahead and run these in Data Explorer here. And yes, it's all looking good. Good. And so now let's move ahead into our next unit test. Let's go back to the controller briefly. And let's see what else we got there. Perhaps this and back in the controller, and what we want to test now is our first metal here, the one that returns all the items available in the REST API in the repository. So remember, these items will just go ahead, retrieve all the items, transfer them into DTO. And then he returns and that's all he does. So what we want to verify is that for any of the items that we that we set up for the suppository that we have to set up the depository, they have to be returned as details, and they should match exactly the IDs that will obtain through depository. So let's go ahead and write a unit test for that. So back into the test class. Again, I'm going to copy the heater of these this case. And I'm going to scroll down here, Copy that. Okay, so here, let's do the proper naming. So these are going to be named get I get items a think are these going to be just with exceed with existing items. He will say, returns or items. And again, let's bring in our arrange, act and assert sections. And in this case, what we need is of course to get a series of items from the repository. So what we have to do first is to declare such a set of items are we will for that we'll create a simple array. So what I'll do is I'll say bar expected items equals new and this is going to be an array. And we're going to be using our create random item method here a few times perhaps less bringing in three items. Yep, so that will do it. Very handy method. And now we can go ahead and set up the repository to return those items. So I'll do repository stop, set up where the repo where they bought the gun get it and say saying so get one gig Titan 16 is invoked, which should return the expected items. Then we have to construct the controller. So I'll do it very similar to what we did with the previous this case, I'll just go ahead and copy this line over here. And then we will go ahead and do. So bar, act two items, equals a weight, controller, get items async. Thanks, that should retrieve the items. And now we have to do the comparison. So once again, we can use that very handy method of fluent assertions to do just an equivalence of comparison. So we can say, items should be equivalent to expected items. And once again, we'll have to do the options. Because we like, again, we're dealing with record types. So otherwise things will not work, right. And then options that comparing by members, I. Okay, so with that, let's go ahead and run this test. Let's see what we get.
Yep, this passing. Awesome, close that. Okay, let's move forward back to ISIS controller, it's time to test our great item async method. So this is the one that goes ahead and creates the item in the repository. So in this case, we have to provide great identity as an input. And then we can verify is that we should be returning, we should be receiving as a return added to with a graded item. That's, that's what these metals is supposed to do. And in fact, that return an item should include the ID because it is not provided in there, create that and do so return items will have an ID and he will also have a graded date. So let's write out a test that can verify all these things. So back to items, control tests, they may gain grab the heater off the desk, perhaps so I grabbed the entire thing. Just copy this, and then remove the space and and remove this piece and I removed the solid piece. Okay, now this method will be called just start with the name of the method, which is create item async. And then this will be in this case, the current state, let's name it with item to create, because we're going to be providing the idea that should be created. And the dissertation is that it returns the Create a title. Right? Now in this case, the arrange piece involves preparing the accurate identity. So in this case, it's going to be it will be tricky to try to use a created random item, as we've been doing so far. So we'll be we'll be explicit in this case. So we will say I tend to create equals new create item DTO. And here we'll provide the elements of the query in detail. So we do provide a name, which is just going to be cool nucleoid string, we will provide a price, which again is going to be just random next, with 1000 again, and I think that that will be. So let me go quickly to create a new table with 12 to make sure I've covered the properties we need. So he has a name and the price. Yep, back here. And then in this case, I will actually not need to set up anything in the repository. Because it is not interesting. What happens I mean, for the terms of of this test case, it is not interested to see what happens when the repository is invoked, if there was a really simple act to create the item. So if you look back at I just controller, what's really going to happen here is that Jehovah God needs to call greet, I are amazing. And I will go ahead and gradiated. But I will encourage you to be a bit careful about what you're going to be testing here. Because you could also decide that no, I want to make sure that they created amazing metal is called in the repository. Right. So that so that yeah, I mean, it should get created. But that's going a little bit too much into the details of the test case. So you are getting you will be making your test case to be very prone to needs changes in the case of the implementation of this method changes, right. So ideally, you want to treat each of your test cases in such a way that they only provide some inputs to the method. And then eventually they validate the outputs of the method but they don't try to make Some just sort of what is going to happen inside. So in this case, we are not going to be carrying all this metal at all. So in fact, we're not going to be setting it up, we will go ahead and go back to this case, we'll go ahead and use invoke, invoke the action. So we will say, var greeted item. Sorry, it's going to be bar result equals weight, controller dot, create item aceing. We provide the item to create. And then we can go ahead and do the assert. And the first thing we're going to do here is try to retrieve that identity. So what I'll do is, I'll do we did item is the result that result as created an action result. Okay, that's the type of result we're going to receive here is a created action result. From that one, we want to get the value. And that value will turn it into an item we do a little bit convoluted for for this case. And now we can do the same equivalence thing that we've been doing so far. So I tend to create
should be equivalent to created by, okay, and then we provide options. Like we said, there will be options that comparing by members. And in this case, the data we're going to use here is actually I can be deal.
Okay, which is the, the type of the object that we received from the front desk
of the creditor. Okay. But additionally, we want to do one more thing here, because a, both of these a DTS don't have the same members, if you remember, if we go to F 12, to identity, Oh, this one has four properties, as you can see. But if we go into great identity, or this one has only two properties over here. So that means that if you try to compare things like this is going to fail, because you're going to say, Hey, what are you writing has more properties than the other. So this, this doesn't make sense. So in these cases, what you can do is say, only look at the properties that are common between the two objects. And that will simplify things in this case. So that's all we can do. And then in the spirit of fluent assertions, we can use to dot excluding missing members. And that should do it for that for that one assertion. Okay, so we're comparing that the two objects are equivalent, but we will not pay attention to any members that are missing from for any other. However, we may want to actually check for those additional members. So for those who will be will be a bit more explicit. So credit item, the ID should not be empty. Because the the method, it should generate an ID for for the scooter rider, and also create an item that create a date.
Should
we don't know exactly what, what day we're going to get in there. But we know that it should be, it should be close to the current time, right? Because this test just take milliseconds to execute. So we will say datetimeoffset UTC now. And then, just to be safe, we're going to give it a range of precision, because we don't know exactly how much time did basically today, although it should be super fast. But we're going to give it 1000 milliseconds to for for the difference between the times of when the item is created and the data we're checking here. Okay, so that should be should be it for this test. Let's go ahead and run it from the test. see what we get. Test is passing. It's looking good. Okay, so the next test we're going to look at, but less case we're going to look at is the case of the update, update item async. Does remember this method, it gets at the item to update, it will get it from the database. And if it doesn't find it, it will say not found. And otherwise, it will create a copy of the item to become the updated item with the updated properties. And then it will go ahead and update the item in the database and finally returns no content. Okay, just to be to not spend too much time here, we're only going to be covering one case here, which is the case of where the item actually exists. But you can you can imagine how to test for the not found case, which is a bit simpler. But then again, don't don't get into the trap of verifying that the item is actually being sent to the date itemizing method of a depository. We want to we will provide an input which is these two parameters here. We expect the output of no content of added that's everything that we need to verify here. Nothing else, we don't need to worry about the implementation of the method. So let's go back to I just got a test. And let's bring in again, just I'll do a copy of our last test over here. And so, this is going to be update, I think we take existing item returns no content, okay. And then we will meet these who need a controller will need that, we will remove this species at the end. So for the range base, as you remember from this controller, we will need a when good imec, Usain Bolt, you need to return it right so that we can move forward into this case. So, I think that that situation we covered already somewhere else. So, let me go back a little bit up. So this one here, so, we will grab these from the get item async. This case, let me grab that here. So, that would allow us to have an item to be returned by the by the depositor is done. So we will name these one, please thing item ID, that's the ID that exists in there. And now we need to declare the the actual item that we're going to provide to the methods. So they updated it. So let's first grab the item ID as a variable. So the item ID is system ID that ID. So we'll grab that there. And then I tend to update
these a new update ID to and so we're going to provide a name naming scheme to be cool if you go it to string. And perhaps
we download line. And then for the price we can do is just pick the price of the existing item price and just increase it a by somebody, let's say by three. Okay, so that becomes the updated paper from this item. So we pretty much were changing the name because we're getting really unusual, we're changing the price by adding three to it. Then we create the controller. And I started to do the the action. So we'll say result equals await controller that update async. And then here's where we provide the item ID. And then we provide the item to update. And then finally we go for the assertion, the assertion is going to be very simple, because like I said, we only want to verify that we we get no content. So result should be of type. No content result. That's all it is. So again, we set up the return of the item from the repository, we prepare nine out of date, we modify the properties, we invoke a bit of the async. And at the end, if everything went well, we should be getting the content result. So let's go ahead and run this test. All best, I think we're missing just one method at this point. Let's go to IRS controller. So this is delete I basic, right? It's got to be very simple. To verify and very similar to update, we get an ID, it will have to a Find the Item. Again, we're not going to check all the cases, for this case, just a case where the item exists. So we'll make it so that it returns the item. And then it should return no content. And again, we don't care what happens with the repository or any of the internals here, we just care about the fact that it should return the content. So back to I just adore tests. I'm going to copy your lastest case, once again.
This is going to be named delete it may sync with existing item returns no content. Yep, that's an appropriate name. The setup will work just fine for this case. But then we don't have to prepare any item to update us do that. Create the controller with those setups. And then we'll do the database sync with the it will be just the existing ID and that ID and then the assert is exactly same as before. There's should be of type knock on result. That's all it is. So I'll go ahead and run this test now.
And Yep, it is passing. And in fact, if we go to our test Explorer, now, let's go to the test section here. And let's run this. As you can see, we have a full suite of tests passing at this point. Okay, so this is great, we have a bunch of test cases covering many, there's nothing in our controller. And what this gives us now is actually a lot of confidence or making any future changes across the, across the REST API. So what I'd like to do now is, there's there's been a few changes that I wanted to make across the board. But I didn't, I didn't have a way to make sure that didn't break anything. But now we do. So I'm going to make two critical changes here. One of them is that I'm going to switch our entity to not be a record type anymore. So remember, he's here if you go to the entities item. So the fact that he's a record type is actually making things a bit inflexible, I mean text. Because of this, we cannot use update existing items in the update a operation in the controller, and we have to create this copy that's not really needed. So these entities should be actively moldable. So we don't need these any stuff here, really. So we'll switch this to be a normal class. And we will also add a description property here, so we can provide some description for the items. So let's make those two changes. And let's see if our test cases can help us prevent breaking anything. So like I said, This item is going to change from record to class. And then we will not be using it here. But we will switch it to just set. So set in all of this. And now this is a normal standard class. But also, like I said, we're going to be adding a description, description for the entity. So with that done. The other thing that I like to do, actually, is to simplify the way that we use videos. Remember that we have these three files for the three videos. But it turns out that there's a much better nicer way to declare the video such as record types, which actually provides even more benefits that we are doing today. So what I'll do is I'll actually just get rid of all these files. Let me just delete the CTOs folder. Perhaps collapse these a bit, what I'm going to do is create a new file at the root of catalog API. Let's name it. videos at CS. Let's bring in some namespace, right namespace, which would be catalog that KPI details, we will declare all the details here in line in a much more a nicer way. Let me hide the sidebar. So for the first video is going to be the item detail. So we will say what do you record it to, then we will declare the properties as if we were creating like a constructor for for this property. That's something that you can do that we will say good ID. And then let's see if we're missing this system. And they will do name the new description property price, and they time offset created date. Okay, so that's you know, that's that's what you need to do to create to declare a record type in this other syntax. So as you can see, is much, much more simplified. Now, let's declare the next one is going to be only record, create item DTO. And then this is going to have no ID but the name and then the new description, property. And then the price. Let's not forget that we had set up some attributes here to make to make sure that we get valid inputs. So in the case of names, we want it to be required. It's amazing the space and in the case of price, what we want is 25 range. So the range Yeah, it was from one to 1000 Okay, we will not require description, and we will not require we will not add the required attributes to price because price is actually validate. So it cannot ever be no, you will get somebody there anyways. And we will just verify that the range is correct. Lastly, let's add our last detail that we're missing. I just copy that one. And this is going to be updated in detail. Yep. So that's all we need. So now in one file we have we're declaring all the details that we're using across the REST API as opposed to three files. Let's see what else we need to modify. So now that we make those changes, let's look at extensions. Let's see what's going on here.
Yeah, so I'll hide cyber. So now that we change the way that we declared our details as record types, they have become immutable. We cannot go through this initialization anymore. We must create the DTO via the constructor. It's the only way to create instances of them from here on. And after construction, nobody can change the properties of the DTO. So we will say new identity Oh, and then we will provide it that ID. Id and the pain. Right? And that scription. I think that price, an item that created date, right? That's always Yep, we got liquid in line these over here, but it may be too much to read. So we'll just leave it like this. Okay, let's see what else we have to fix. If we go back, so there's something going on in the controller? Let's see what it is. Yep. So there's an issue with update imazing? Yep. So we cannot use this syntax anymore. Because the item is no longer a record type. Now it is a standard class. And in fact, this is this is good. This is actually exactly what we wanted to fix here. Because there's no need to create this updated item, we could just modify the existing item. So we will say, existing item that name equals item D do that name, an existing item, the price equals identity or that the price. Yep. So we don't need these additional guy here. We'll leave it like that. And then for the update, we'll use invoke existing IRA. Okay, so yeah, there's no more breaks around here. Let's see what else. So because of API, everything looks good in the unit tests. Let's see what's going on. Okay, let's hide the sidebar. Let's see. Yep. So great identity Oh, has to be weighted to define the properties inside the constructor. So we'll have to provide these for the name. And they probably will do the same thing for the description. And for the price, the random number, perhaps, so I'll delete this. And then I'll do it like this. That is it is easier to see. There. Okay. So let's see what else we have to fix here. Just in case for update identity. Oh. So let me copy this line here. So we have to provide that then we provide a description. And then we provide the price, which is going to be existing item price plus three. Okay, remove this. Yep. And with that, I think we don't have any more breaks are not in there. I can see actually. And so let's go ahead. And now let's run our tests and see what we get. So go ahead and run the tests. Yep. And, interestingly, we do have something failing here. Let's see what's going on. So great. It may sync is the one that's failing. And as you can see, we are getting the x over here, signaling that there's a problem. We get the squiggles here, signaling that there's something in there. And if this if we see the message over there, you can see it says expected member description to be no, but found some value in there. Yep. And then, so what could have happened? So let's actually go back to our controller. So I'll do f 12, from the controller. And let's go to the Create method. Over here. Let's hide this hide sidebar. And, yeah, so what happens here is if you can spot that is that we have not a percentage value for the description. So let me actually close this close these. So back to the test. Yep. So when we declared the item to create, we did add a description. But when we created the graded item, it does not have any value for description. That is because we have not specified that here anywhere. So that's something we have to fix. And that's, that's a nice thing about using these should be equivalent to because as you add more properties, and you don't have to be remembering to do the proper checks in the test cases, right. Otherwise, anytime you you add a new property here, you have to go back to the test and other properties in there to make sure that you don't Forget what it is that this case is actually covering you, making sure that you don't forget to add those properties in the place where you're implementing the Edit method. For this case,
I'll do description equals identity to that description. With that, let's go back to our test list, lets us run them again. It's all green now. And then one minor improvement that you can also do here is to change the way that we're doing some comparisons across a few test cases. So we go back to two lists test case, erasing, we created a retrospective item. If you see how we're doing B, A k be equivalent to here, these options, we need to do them because the item A during class A we're able to actually record before, so that was causing issues. Now that it's not a record type, we can stick to the normal behavior. So now we can just remove this and do the equivalent to this way. And fearless agents will know what to do. Because this is just a standard plus the same way we can go to the next case, and change this think. And that should be enough. And this is only because like I said, because what we're comparing here is the other classes are not record record type, you still have to do the other way. So let's just make sure that the test cases are still passing. So I'll go ahead and run all the tests. And everything is still all green. Okay, so that's working pretty well. And now I wanted to I'd like to switch gears into test driven development TDD. So we talked about TDD, it has some very nice benefits, because it allows you to start for the test for the requirements really, and then move forward to implementation later on. So let's, let's see, how do they work. In practice, what we're going to do is if we go back to items controller, and I'll hide these for a moment again, let's go back to our first method here, get items async. So as you see, this is the method that returns all the items. But a new requirement to our REST API is that we should be able to return a the items by a by the name. So if somebody specifies a name into into such a method, which should be able to return only the items where the name contains the specified parameter, right. So So if the if the, if you have items that have for instance, a word potion in them, like a potion, potion, potion, all these all these things, which are going to return all the items that include potion in the name, and not to the right, so it's a way of filtering things. But let's see how we can go ahead and implement such a method by using DDD. So I'll go back to I just controlled tests. So let's grab this, grab this method, get it and pick it as a sink. And then I'll just copy that just under it because there's similar methods. But in this case, we rename it to get either a sink, we will say, with matching items, returns,
returns
matching items. Okay, so in this case, we're going to go ahead, and we will not be using the random item anymore, because I actually want to specify a name for our items. And in fact, that's the only thing that we cared about, in the case of this test case. What do we want explicit here. So what I want to do is rearrange this a little bit, so that instead of these, what we have is, let's name these, all items are all items equals new. And then I'll just move these to the next line is going to be a bit more verbose. Like this perhaps. And then, in each of these lines, instead of these, we're going to create new items, new items, so we will say new item. And he will will provide a name. So let's use something that we can use for these tests. Like I said, Let's go for the potion. potion case. So we will have a potion there. And then let's add two more. The second one is going to be named Let's name it something completely different, like how to do it. And then the last one is going to be highbush. So in this case, we have two items that have the same, the same term portion and another one that doesn't have it. And then we'll declare a variable here that we're going to say its name, name to match equals Now notice that we are already implemented this case for this new method, but the method just does not exist at all, he has not been implemented. And that's the right way of doing DDD. So we will start with a test case that will actually fail because we don't have the method, and then we will move forward implementation later. Okay, so now we're depositary inbox, get items async, we will return all the items that we have prepared in here.
Remember, this is the call to the opposite, it is not the call to the controller get is async. So then we go ahead and prepare our controller and then a constant time to make the call to the act.
Now here, we're going to be a bit more explicit. The first it will now go to us in bar on the left side, because we want to signal to the to C sharp and VS code, what we're going to receive from this new method or method to be created. So in this case, what we're going to receive is an ienumerable. of item DTO. Are these going to be found items? And are we missing something? Yeah, system collection generic was amazing. And then these are going to come by calling weight controller, get items async. But not the signature, we need a new signature, where we can actually pipe them async we need a new signature, we can receive the name to match. So we're going to pass name to match here. Okay. And yeah, so yeah, that's got to be great. And then as, as our assert, what we have to verify is that we only got items that were their name, match the name to batch in this case potion. So to verify that we will say phone items should only contain. And then we can say item where item that name should be equals to item, it will be items soup, sorry, so this is all items, sub zero dot name, because that's the first one, let's scroll up a little bit. So we got zero, and we got one and two. So it should be that one. Or, and then just copy this should be I think name should be equals to a lightened suit to that name. Okay, so that's a way that you can use fluent assertions to verify that the items in the collection match some condition. So whatever we're getting, we're getting four items, a, the item should match either phusion or hipolito. In this case, okay. So if we go ahead and build this, I'll do Ctrl Shift v. Course it fails. Because we don't have such a metal gate is a thing that receives some argument. So that's kind of the red face of DDD. And now to start moving into the green phase, we need to move forward and implement implement this is meant to do that, or we can do is just click here, I'll do a Ctrl dot. And that a presents his options or we can do is use a generate method, I just controller get get isolate async. So I'll do that. And then if I do f 12. Here, the method now exists. And I'll move this up just next to the one that we have already the other overload here. Okay, and then we could go ahead and try to implement these. But as you notice, these methods are exactly the same thing. But one of them receives a parameter and the other one does not receive it. So I think it's better to just put everything in this one method, as opposed to try to implement the second one. So I'm going to just take this parameter out of here and into there. Are there Okay, and then we are not expecting to always receive the name, it depends on what the caller wants. So let's make so that these a can receive no, so it is allowed to receive. Okay, and so with that, let's go ahead and run or this case a once again, let's see what we get. I mean, at this point, you should build just fine this verify that should build Yeah, it does build. Now let's run the test. And in our suspected it is failing. It is failing because it is carrying a more items than expected. And on this as a result found items, just getting all the items is not getting only the ones that have been specified here. So let's see how we can fix this. So let's try to get green. So what we're going to do is something very simple after retrieving the list Both items, we're going to apply some filtering if we have to apply a filter. So if string.is null or whitespace. Name, name to match. That only list, let's just rename these to name as opposed to need to match. That should be enough. If name is more whitespace if it is not known as whitespace, then we will apply a filter on the list of files we have already received. So items is going to be items where item
height in duck name, contain corn contains the me. And just to make sure that we don't, we don't worry about casing here. Let's do strings comparison already. Now, in our case, we that it doesn't matter. If you're looking for potions for with capital P or with smaller p, it shouldn't matter. We don't we don't care about that. So as long as as a name has been provided able to use it to do a filter on the items, so we get a filtered list of items. And that should be enough to satisfy the condition. Let's go back to the test. Let's run it. And this time it is passing. So we are agreeing, this actually confirmed that this is true by running the entire test suite now. And making sure that we have not broken anything. Yeah, as you can see, everything is green now. And so everything looks look great. So that's how you how you can use a DDD. We don't really need to do more refactoring here at this point. But if you needed to you feel free to go ahead and do more refactoring. But we have gone through the red phase, green phase. And now refactoring is not not dealing this case. What we can do now is to verify that this new functionality actually works in the in the real life. So we will start our host, and we will see how to use these from postman. Okay, so I'll go ahead and hit f5. And then going to postman are here. So let's see. To start with, we'll see what we have currently in our database. So I have here the URL to get all the items in the database. So I'll hit send. And at this point, we have a potion and antidote. And we are ready verifying that things are not broken, because we already in both this method that has been modified, it can receive a parameter now, but it seems to be working just fine. So to properly verify that the new functionality is working, I'm going to actually add a yet another another item here via the post action. So I'll just copy the URL and say plus, I'll switch this to post, pay the URL and in the body of the raw. And I'll do Jason. And then I'll just copy the body of something else here, copy this, notice that the previous items don't have a description, and that's fine. And the new item will have one let's make sure we can have one. So let's do this. That. And then to keep things simple, I'll just name this guy potion. And this is going to be stores a small amount of HP. And the price is going to be let's say seven. So let's pause this. Okay, is there. And if we go back to our get a operation I hit set, here we can see that we have the three items now create now is where we can see if the filtering is working. So now I can say that name equals and are we looking for potion right? So portion, so they should only give me the mega petition and abortion. So I'll go ahead here and hit send. And indeed, we are only getting these two items. Notice that it didn't matter that I use a smaller p here and not a capital P that we have earlier, he was still able to find the items. So things are working as expected. So yeah, that's how you can use DD to drive your your process to add new functionality to the REST API. So as always, I hope this was useful. And if you're looking to dive in deeper into what I have covered in this tutorial series, please check out the link to my full online course in the video description that's been watching and I'll see you next time.