VM Insights : Surveiller les performances et dépendances des VMs Azure

Surveiller ses environnements Azure est très important. Pour cela nous connaissons bien la solution Azure Monitor qui nous permet justement de surveiller et analyser nos environnements Azure. Azurer Monitor nous permet également d’être alerté si un incident survient sur notre plateforme.

              L’une des nouvelles fonctionnalités d’Azure Monitor est VM Insights. Elle nous permet de visualiser les performances et les dépendances de nos machines virtuelles. VM Insights se base sur les logs de VMS récupérés par les collecteurs de données Log Analytics.

              Voici les points qui seront traités :

  • Comment activer VM Insights : depuis le Portail et en PowerShell
  • Comment embarquer les VMs dans la solution : depuis le Portail et en PowerShell
  • Gérer les alertes en PowerShell

A l’issue de cet article vous saurez comment activer la solution et y embarquer les VMs automatiquement avec un script PowerShell complet et expliqué.

Activation de VM insights :

Il existe différentes méthodes pour activer VM Insights parmi lesquelles :

Directement sur le portail Azure

  • En ajoutant la solution au niveau d’un Log Analytics Workspace
    • En activant la solution depuis la machine virtuelle : dans le volet Monitoring de la VM > Insights > Enable / Activer.

L’activation de VM Insights directement depuis la VM, permet, en même temps d’activer la fonctionnalité VM Insights et d’embarquer la VM dans la solution. Une fois la fonctionnalité activée nous avons une visualisation des dépendances et performances de notre machine virtuelle comme représenté sur les Dashboards ci-dessous :

Visualisation des performances : Indicateur sur l’utilisation de la CPU, sur les espaces disques, les IOPS ou encore l’utilisation de la mémoire.

Visualisation des dépendances : nous permet notamment de voir avec quels environnements échange la VM et sur quels ports ou encore quels processus sont lancés sur la VM.

En ligne de commande

              Comme expliqué précédemment, la fonctionnalité VM Insights se base sur un Log Analytics Workspace. Nous devons donc commencer par en créer un ou récupérer les informations du Workspace déjà existant que l’on souhaite utiliser, ce sera le cas dans mon exemple. Voici le code permettant de récupérer les propriétés d’un Workspace existant puis d’ensuite activer la solution VM Insights :

# Get Log Analytics Workspace properties
$law = Get-AzOperationalInsightsWorkspace -ResourceGroupName $rgName 

#Set in a variable VM Insights solution Name
$SolutionName = "VMInsights"
 
#Enable VMInsights Solution on the Log Analytics Workspace
Set-AzOperationalInsightsIntelligencePack -ResourceGroupName $rgName -WorkspaceName $law.Name -IntelligencePackName $SolutionName -Enabled $true 

Comment embarquer les VMs dans la solution

              Pour embarquer des VMs dans la solution nous pouvons le faire comme expliqué précédemment directement depuis la VM ou bien via PowerShell. Il est important de comprendre comment le lien se fait entre les VMs et la solution VM Insights. Lors de l’activation de la solution pour une VM, 2 agents sont poussés dans la VM. Les agents sont les suivants :

  • Pour les VMs WINDOWS : Microsoft Monitoring Agent et Dependency Agent Windows
  • Pour les VMs Linux : Oms Agent For Linux et Dependency Agent Linux

              Si l’on souhaite déployer VM Insights sur un ensemble de VMs, nous n’allons pas passer par une activation de la solution à la main VM par VM depuis le portail. C’est tout l’intérêt du script PowerShell qui suit :

              Nous commençons par récupérer une liste de VMs pour lesquelles nous souhaitons analyser les performances et dépendances. Ici nous récupérons toutes les VMs d’un groupe de ressources :

# List all VMs in the resource group 
$azVMs = Get-AzVM -ResourceGroupName $rgName

              Ensuite, nous automatisons le déploiement des 2 Agents sur chaque VM de notre groupe de ressource en fonction de l’OS de la machine. Deux paramètres sont importants pour pousser l’installation des agents : l’ID du Workspace et la clé d’accès à ce dernier. Ce qui donne le code suivant :

$PublicSettings = @{"workspaceId" = $law.CustomerId}
$ProtectedSettings = @{"workspaceKey" = $lawKey}

# Push Agents Install on VMS to enable VM Insights
foreach ($vm in $azVMs) {
    # get VM's Os type

    $OsType = $vm.StorageProfile.OsDisk.OsType

    # Filter installation extension name by the Os type of the VM
    if($OsType -eq "Windows"){

        Set-AzVMExtension -ExtensionName "MMAExtension" `
            -ResourceGroupName $rgName `
            -VMName $vm.Name `
            -Publisher "Microsoft.EnterpriseCloud.Monitoring" `
            -ExtensionType "MicrosoftMonitoringAgent" `
            -TypeHandlerVersion 1.0 `
            -Settings $PublicSettings `
            -ProtectedSettings $ProtectedSettings `
            -Location $location

        Set-AzVMExtension -ExtensionName "DependencyAgentWindows" `
            -ResourceGroupName $rgName `
            -VMName $vm.Name `
            -Publisher "Microsoft.Azure.Monitoring.DependencyAgent" `
            -ExtensionType "DependencyAgentWindows" `
            -TypeHandlerVersion 9.1 `
            -Settings $PublicSettings `
            -ProtectedSettings $ProtectedSettings `
            -Location $location

    }

    if($OsType -eq "Linux"){

        Set-AzVMExtension -ExtensionName "OMSExtension" `
            -ResourceGroupName $rgName `
            -VMName $vm.Name `
            -Publisher "Microsoft.EnterpriseCloud.Monitoring" `
            -ExtensionType "OmsAgentForLinux" `
            -TypeHandlerVersion 1.0 `
            -Settings $PublicSettings `
            -ProtectedSettings $ProtectedSettings `
            -Location $location

        Set-AzVMExtension -ExtensionName "DependencyAgentLinux" `
            -ResourceGroupName $rgName `
            -VMName $vm.Name `
            -Publisher "Microsoft.Azure.Monitoring.DependencyAgent" `
            -ExtensionType "DependencyAgentLinux" `
            -TypeHandlerVersion 9.1 `
            -Settings $PublicSettings `
            -ProtectedSettings $ProtectedSettings `
            -Location $location

    }

    else{

        Write-Host "OS Type :" -ForegroundColor Red -NoNewline
        Write-Host $OsType -ForegroundColor White -NoNewline
        Write-Host "  not supported " -ForegroundColor Red -NoNewline
    }

} 

Création d’alertes

              Pour optimiser la surveillance de la plateforme et pouvoir suivre au mieux les performances de nos VMs, nous pouvons créer des règles d’alerte dans Azur Monitor en se basant sur les insights des VMs. Ici nous allons créer une alerte nous permettant d’être notifié par mail dès lors que l’utilisation de la CPU d’une VM dépasse 80%.

Créer un groupe d’action

              Pour commencer nous devons créer un groupe d’action dans lequel nous allons indiquer quelle action devra être effectuée lors du déclenchement de l’alerte, dans notre exemple un envoie de mail.


# Add new email where alerts should be send
$email = New-AzActionGroupReceiver -Name "alerts-mail" -EmailReceiver -EmailAddress "perfAlert@infeeny.com"

# Add Action group
$act = Set-AzActionGroup -Name "performance alerts" -ResourceGroup $rgName -ShortName "perfalerts" -Receiver $email

#$act = Get-AzActionGroup -ResourceGroupName $rgSocleName -Name "plateform alerts Action Group"
$action = New-AzActionGroup -ActionGroupId $act.id

Ajouter une règle d’alerte

      Maintenant nous créons le critère de l’alerte avec la métrique que nous souhaitons surveiller. Puis on crée l’alerte à laquelle nous allons associer ce critère et le groupe d’action créé juste avant.


### Add metric Rule for CPU ###

#set alert criteria for CPU utilization 
$criteriacpu = New-AzMetricAlertRuleV2Criteria -MetricName "Percentage CPU" `
-TimeAggregation average `
-Operator GreaterThanOrEqual `
-Threshold 80 

#Add alert rule     
Add-AzMetricAlertRuleV2 -Name "Windows and Linux CPU Alerts" `
    -ResourceGroupName $rgName `
    -WindowSize 00:05:00 `
    -Frequency 00:01:00 `
    -TargetResourceScope $rg.ResourceId `
    -Condition $criteriacpu `
    -TargetResourceType microsoft.compute/virtualmachines `
    -TargetResourceRegion $location `
    -ActionGroup $action `
    -Severity 3 

Conclusion

              Pour conclure, grâce à la nouvelle fonctionnalité VM Insights d’Azure Monitor, nous avons un suivi et une vision complète des performances de nos VMs. Cette fonctionnalité nous apporte des Dashboards complets que nous pouvons customiser afin d’afficher les métriques qui nous sont le plus cohérentes.
              Dans cet article, vous avez toutes les informations nécessaires pour déployer la fonctionnalité VM Insights sur vos VMs Azure et générer des alertes vous permettant ainsi de réagir au plus vite en cas d’incident ou de surcharge. Ici nous avons choisi PowerShell mais il est tout à fait possible de déployer la solution via Terraform ou Azure ARM si vous faites de l’InfrasAsCode.

Voici le lien Github vers le script complet :

https://github.com/infeeny/VMInsights/blob/master/VMInsights.ps1

L&L Kubernetes et AKS

Retrouvez notre Expert Christophe, dans une vidéo sur Kubernetes et AKS. La vidéo est Level 100, accessible à tous. URL sur Youtube: ici

Contenu: Il s’agit de prendre un projet Web API sous Visual Studio 2019 avec support Docker (container Linux) et de construire l’image Docker en local. Ensuite on pousse cette image dans une registry ACR afin de faire un déploiement dans un cluster AKS. Ensuite, on aborde kubectl, l’outil console de Kubernetes.

Pour réaliser toutes les opérations techniques, un HOWTO est à télécharger sur https://infeeny.com/net-core-sous-aks/

Développement Web ASP .NET Core

Bonjour, dans cet article, nous allons vous présenter la solution eShopWeb, voici comment va se décomposer l’article :

    • Architecture monolithique
    • Design Pattern MVC
    • Entity Framework Core
    •  eShopWeb
      • Introduction
      • Explication de la fonctionnalité Add to Basket
      • Explication de la fonctionnalité Checkout

Architecture monolithique

Le terme monolithique est plutôt utilisé lorsque l’on parle de conception d’application (ou architecture) et représente le modèle dit « traditionnel » d’une application. On peut considérer ce genre d’architecture comme un bloc unifié (contrairement aux micro-services).

Néanmoins, on peut mettre différentes couches de données (par exemple du MVC, mais nous reviendrons sur l’explication de se terme plus loin dans cet article). Ce qui signifie que les différents composants sont intrinsèquement connectés les uns aux autres et sont la plupart du temps dépendant de chacune des couches existantes.

L’inconvénient principal est lors de la mise à jour d’un élément, il faut alors réécrire une bonne partie de l’application (si ce n’est son entièreté). Par opposition, l’architecture monolithique permet un meilleur rendement que l’architecture micro-services. Mais surtout l’aspect monolithique permet de facilité les tests et le débogage car il y a moins d’éléments à prendre en compte.

Patron de conception : Modèle – Vue – Contrôleur (MVC)

Ce qu’on appelle en français un patron de conception est appelé en anglais design pattern. Et celui du MVC permet de séparer la logique du code :

  • Modèle : Un modèle permet de gérer les données du site. Il va donc chercher les informations dans la base de données, les organiser et les assembler pour ensuite être traité par le Contrôleur.
  • Vue : Une vue va comme son nom l’indique s’occuper de l’affichage. Il n’y a quasiment aucun calcul dans cette partie car son seul but est de récupérer des variables pour savoir ce qui va être affiché.
  • Contrôleur : Un contrôleur va gérer la logique du code. Il sert d’intermédiaire entre le modèle et la vue. Globalement, il va demander au modèle les données et les analyser puis il renvoie le texte qu’il faut afficher à la vue.

On peut, en complément du Contrôleur, avoir une couche que l’on va nommer communément Service, qui va se charger de la récupération de données pour une API par exemple.

Entity Framework Core

Entity Framework Core est un ORM créé par Microsoft, il vous permet d’effectuer des opérations CRUD sans avoir besoin d’écrire des requêtes SQL.

Pour reprendre à la base : qu’est-ce qu’un ORM ?

ORM signifie Object Relational Mapping ou Mapping Objet Relationnel en français. C’est une couche supplémentaire à notre application.

Mais qu’est-ce que ça signifie concrètement : que ça se place entre un programme applicatif et une base de données relationnelle afin de simuler une base de données orientée objet. On va donc associer une classe et une table et pour chacun des attributs de la classe qui vont correspondre à un champ de la table associée.

Avantages

  • Réduction de la quantité de code est réduite et que l’on gagne en homogénéité avec le reste du code (pour les langages orientés objets).
  • On va pouvoir travailler directement avec des objets complexes.
  • Plus besoin d’écrire nos requêtes SQL.
  • Moins de travail pour les développeurs

Inconvénients

L’utilisation d’ORM, induit une couche logicielle supplémentaire ce qui forcément nuit aux performances de l’application et rend particulièrement délicate la maintenance de l’application. Ainsi que la durée de vie de l’application.

Modeling

Entity Framework Core va via une commande « mapper » des classes complexes. Il va donc faire une requête à la base de données et remplir les données de nos classes associé au même table.

Les classes à remplir s’appellent des Modèles. Voici un exemple dans la solution eShopWeb :

namespace Microsoft.eShopWeb.Web.ViewModels
{
  public class CatalogItemViewModel
  {
     public int Id { get; set; }
     public string Name { get; set; }
     public string PictureUri { get; set; }
     public decimal Price { get; set; }
  }
}

Comme vous pouvez le voir, ce Modèle correspond aux informations relatives à un objet présent dans le catalogue.

Maintenant, voici l’objet Catalog qui contient tout les items et d’autres informations :

using Microsoft.AspNetCore.Mvc.Rendering; using System.Collections.Generic; namespace Microsoft.eShopWeb.Web.ViewModels {   
  public class CatalogIndexViewModel   
  {     
    public IEnumerable<CatalogItemViewModel> CatalogItems { get; set; } public IEnumerable<SelectListItem> Brands { get; set; }     
    public IEnumerable<SelectListItem> Types { get; set; }     
    public int? BrandFilterApplied { get; set; }     
    public int? TypesFilterApplied { get; set; }     
    public PaginationInfoViewModel PaginationInfo { get; set; }  
 
  } 
}

Pour pouvoir le remplir (binding), nous allons donc maintenant voir un nouvel exemple :

using Microsoft.eShopWeb.ApplicationCore.Entities;
using System.Collections.Generic;
using System.Threading.Tasks; namespace Microsoft.eShopWeb.ApplicationCore.Interfaces {   
  public interface IAsyncRepository<T> where T : BaseEntity, IAggregateRoot   
  {     
    Task<T> GetByIdAsync(int id);     
    Task<IReadOnlyList<T>> ListAllAsync();     
    Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec);     
    Task<T> AddAsync(T entity);
    Task UpdateAsync(T entity);     
    Task DeleteAsync(T entity);     Task<int> CountAsync(ISpecification<T> spec);   
  } }

Ci-dessus, c’est là où nous faisons appel à EntityFrameworkCore.

Et dans le Service : CatalogViewModelService.cs

public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId) {   
  _logger.LogInformation("GetCatalogItems called.");   
  var filterSpecification = new CatalogFilterSpecification(brandId, typeId);
  var filterPaginatedSpecification =   new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId);   // the implementation below using ForEach and Count. We need a List.   
  var itemsOnPage = await _itemRepository.ListAsync(filterPaginatedSpecification);   var totalItems = await _itemRepository.CountAsync(filterSpecification);   var vm = new CatalogIndexViewModel()   
{     
  CatalogItems = itemsOnPage.Select(i => new CatalogItemViewModel()     
  {        
    Id = i.Id,        
    Name = i.Name,       
    PictureUri = _uriComposer.ComposePicUri(i.PictureUri),        
    Price = i.Price     
  }),     
  
  Brands = await GetBrands(),     
  Types = await GetTypes(),     
  BrandFilterApplied = brandId ?? 0,     
  TypesFilterApplied = typeId ?? 0,     
  PaginationInfo = new PaginationInfoViewModel()    
  {       
    ActualPage = pageIndex,       
    ItemsPerPage = itemsOnPage.Count,       
    TotalItems = totalItems,       
    TotalPages = int.Parse(Math.Ceiling(((decimal)totalItems / itemsPage)).ToString())     
  }   
};   
  vm.PaginationInfo.Next = (vm.PaginationInfo.ActualPage == vm.PaginationInfo.TotalPages - 1) ? "is-disabled" : "";   
  vm.PaginationInfo.Previous = (vm.PaginationInfo.ActualPage == 0) ? "is-disabled" : "";   
  return vm; }

 

Providers

Un provider n’est ni plus ni moins qu’une librairie utilisable. Dans notre cas, celle qui nous intéresse particulièrement et dont on va parler est la librairie InMemory qui est un Fournisseur en Mémoire.

InMemory Provider

Ce fournisseur est utile lorsque l’on souhaite tester des composants en utilisant quelque chose qui se rapproche de la connexion à la base de données réelle, sans les frais des opérations de la base de données réelle.

L’avantage de ce fournisseur en mémoire et que l’on peut tester notre code par rapport à une base de données en mémoire au lieu de devoir en installer une et de la configurer.

Solution eShopWeb

Nous allons maintenant voir plus en détail cette solution et nous allons nous pencher sur les deux fonctionnalités majeures :

  • Add to Basket
  • Checkout

Add to Basket

Le « Add to Basket » comme son nom l’indique, vous permet d’ajouter un article dans votre panier.

eshopweb.PNG

Accueil du site

Voilà à quoi ressemble le site eShopWeb. Une fois que vous cliquez sur Add to Basket, vous êtes directement redirigé vers votre panier.  via l’URL : https://localhost:44315/Basket

Capturebasket.PNG

Que se passe-t-il au niveau du code ?

L’image représentant l’accueil du site est dans le code, lié au fichier _product.cshtml qui lorsqu’un utilisateur clique sur ADD TO BASKET il sera redirigé ver l’URL suivante : http://localhost:port/Basket

Cette URL correspond au fichier Index.cshtml qui est couplé au fichier Index.cshtml.cs et c’est ce fichier cshtml.cs qui va posséder la « logique » de la page avec une méthode OnPost.

public async Task<IActionResult> OnPost(CatalogItemViewModel productDetails) {   
    if (productDetails?.Id == null)   
    {     
        return RedirectToPage("/Index");   
    }   

    await SetBasketModelAsync(); //Vérifie si l'on est connecté.   
    await _basketService.AddItemToBasket(BasketModel.Id, productDetails.Id, productDetails.Price); //Ajout de l'objet au panier.   
    await SetBasketModelAsync();  
    return RedirectToPage(); }

Dans cette méthode OnPost, on peut voir :

  • Une condition if, afin de vérifier si l’ID du produit n’existe pas
    • Si cette condition se vérifie, l’utilisateur sera redirigé vers l’URL http://localhost:port/Index
    • Sinon, on vérifie d’abord que l’utilisateur est connecté grâce à SetBasketModelAsync()
  • Ce SetBasketModelAsync() va quant à lui, vérifier si l’utilisateur est connecté et s’il a déjà un panier de fait sinon l’utilisateur n’est pas encore connecté mais possède un panier avec les éléments qu’il aura ajouté dedans.
  • Suite à cela, on va ajouter le produit dans le panier en prenant en compte, l’id du panier, l’id du produit ainsi que le prix de celui-ci en passant par la classe BasketService :
public async Task AddItemToBasket(int basketId, int catalogItemId, decimal price, int quantity = 1) //Cette méthode ajoute le produit en premier. {   
    var basket = await _basketRepository.GetByIdAsync(basketId); //Vérifie à quel panier il doit l'ajouter.   
    basket.AddItem(catalogItemId, price, quantity); //Appel de la méthode dans Basket.cs   
    await _basketRepository.UpdateAsync(basket); }

Pour rappel, la variable _basketRepository basé sur la classe BasketRepository correpond à notre Entity Framework Core.

    • Ce même Service (BasketService) va tout d’abord Get le panier via l’id passé en paramètre (le nom du paramètre : basketId) et le stocker dans la variable basket (qui correspondra à une variable initialisée avec la classe Basket)
    • Puis on va ajouter un produit via la méthode AddItem que nous allons voir par la suite et qui prend en paramètres l’id du catalogue, le prix du produit et la quantité (par défaut initialisée à 1).
    • La dernière ligne va nous permettre de charger notre panier dans la base de donnée.
public void AddItem(int catalogItemId, decimal unitPrice, int quantity = 1) //méthode d'ajout 
{
  if (!Items.Any(i => i.CatalogItemId == catalogItemId))
    {
       _items.Add(new BasketItem(catalogItemId, quantity, unitPrice));
       return;
    }
  var existingItem = Items.FirstOrDefault(i => i.CatalogItemId == catalogItemId);
  existingItem.AddQuantity(quantity);
}
  •  le If va nous permettre de savoir si le produit se trouve déjà dans notre liste de produit.
  • Si non, nous entrons dans le If et nous l’ajoutons.
  • Si oui, nous allons simplement mettre à jour la quantité de ce produit.

 

Passons maintenant à la partie Paiement (Checkout)

Il existe deux cas pour cette partie :

  • Soit vous êtes connecté
    • Le bouton CHECKOUT dans le fichier Index.cshtml va vous emmener sur le fichier Checkout.cshtml.
      • Ce fichier est lié au fichier Checkout.cshtml.cs qui va s’occuper de toute la logique.
      • La méthode dans Checkout.cshtml.cs qui nous intéresse est la méthode OnPost :
public async Task<IActionResult> OnPost(Dictionary<string, int> items) {   
    await SetBasketModelAsync();   
    await _basketService.SetQuantities(BasketModel.Id, items);   
    await _orderService.CreateOrderAsync(BasketModel.Id, new Address("123 Main St.", "Kent", "OH", "United States", "44240"));   
    await _basketService.DeleteBasketAsync(BasketModel.Id);   
    return RedirectToPage(); 
}
        • La méthode SetBasketModelAsync() comme précédemment va vérifier si vous êtes connectés ou non en tant qu’utilisateur
        • Le _basketService.SetQuantities() va set le nombre d’articles présent dans notre panier
        • Le _orderService.CreateOrderAsync() fait appel au service OrderService et permet l’ajout de notre commande (on va le détailler comment cela fonctionne un peu plus tard).
        • Le _basketService.DeleteBasketAsync() va détruire le panier dans la base de donnée.
    • Pour revenir au _orderService.CreateOrderAsync() :
public async Task CreateOrderAsync(int basketId, Address shippingAddress) { var basket = await _basketRepository.GetByIdAsync(basketId);   Guard.Against.NullBasket(basketId, basket);
var items = new List<OrderItem>();
foreach (var item in basket.Items)
{
var catalogItem = await _itemRepository.GetByIdAsync(item.CatalogItemId);     var itemOrdered = new CatalogItemOrdered(catalogItem.Id, catalogItem.Name, _uriComposer.ComposePicUri(catalogItem.PictureUri));
var orderItem = new OrderItem(itemOrdered, item.UnitPrice, item.Quantity);     items.Add(orderItem); }
var order = new Order(basket.BuyerId, shippingAddress, items);
await _orderRepository.AddAsync(order);
}

Que fait cette méthode ?

  • On get d’abord le panier
  • Ensuite on regarde si le panier est null
  • Nous créons une liste d’item à commander
  • Dans la boucle nous allons remplir cette liste en récupérant les éléments se trouvant dans notre panier
  • Nous allons créer un nouvel objet Order
  • Et l’ajouter dans la base de donner (appel de OrderRepository.AddAsync())

MVC : Comment les interactions sont faites

order.PNG

Mes commandes – Images

 

Cette page est la représentation graphique de la View (vue) : MyOrders.cshtml

@model IEnumerable<OrderViewModel>
@{
ViewData["Title"] = "My Order History";
}

<div class="esh-orders">
<div class="container">
<h1>@ViewData["Title"]</h1>
<article class="esh-orders-titles row">
<section class="esh-orders-title col-xs-2">Order number</section>
<section class="esh-orders-title col-xs-4">Date</section>
<section class="esh-orders-title col-xs-2">Total</section>
<section class="esh-orders-title col-xs-2">Status</section>
<section class="esh-orders-title col-xs-2"></section>
</article>
@if (Model != null && Model.Any())
{
@foreach (var item in Model)
{
<article class="esh-orders-items row">
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.OrderNumber)</section>
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.OrderDate)</section>
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item col-xs-1">
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
</section>
<section class="esh-orders-item col-xs-1">
@if (item.Status.ToLower() == "submitted")
{
<a class="esh-orders-link" asp-controller="Order" asp-action="cancel" asp-route-orderId="@item.OrderNumber">Cancel</a>
}
</section>
</article>
}
}
</div>
</div>

Cette View est directement lié au Contrôleur : OrderController, plus particulièrement à la méthode MyOrders :

[HttpGet()]
public async Task<IActionResult> MyOrders()
{
var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));

return View(viewModel);
}

Comme nous pouvons le voir ici, la première ligne correspond au contrôle de données (ici, nous récupérons la liste des commandes dans la base de données). Puis nous retournons la vue de notre Contrôleur (l’image Mes commandes – Images ci-dessus).

Si nous cliquons sur un des boutons DETAIL, nous aurons accès au détail du produit sélectionné (la sélection se fait par l’id du produit) :

<section class="esh-orders-item col-xs-1">
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
</section>

L’appel à Detail dans le fichier order.cshtmlIci, on va appeler la méthode de l’OrderController s’appelant Detail.

[HttpGet("{orderId}")]
public async Task<IActionResult> Detail(int orderId)
{
var viewModel = await _mediator.Send(new GetOrderDetails(User.Identity.Name, orderId));

if (viewModel == null)
{
return BadRequest("No such order found for this user.");
}

return View(viewModel);
}

On peut observer que dans le Contrôleur, nous récupérons le détail de la commande. Et nous retournons la bonne vue associée :

@model OrderViewModel
@{
ViewData["Title"] = "My Order History";
}
@{
ViewData["Title"] = "Order Detail";
}

<div class="esh-orders-detail">
<div class="container">
<section class="esh-orders-detail-section">
<article class="esh-orders-detail-titles row">
<section class="esh-orders-detail-title col-xs-3">Order number</section>
<section class="esh-orders-detail-title col-xs-3">Date</section>
<section class="esh-orders-detail-title col-xs-2">Total</section>
<section class="esh-orders-detail-title col-xs-3">Status</section>
</article>

<article class="esh-orders-detail-items row">
<section class="esh-orders-detail-item col-xs-3">@Model.OrderNumber</section>
<section class="esh-orders-detail-item col-xs-3">@Model.OrderDate</section>
<section class="esh-orders-detail-item col-xs-2">$@Model.Total.ToString("N2")</section>
<section class="esh-orders-detail-item col-xs-3">@Model.Status</section>
</article>
</section>

<section class="esh-orders-detail-section">
<article class="esh-orders-detail-titles row">
<section class="esh-orders-detail-title col-xs-12">Shipping Address</section>
</article>

<article class="esh-orders-detail-items row">
<section class="esh-orders-detail-item col-xs-12">@Model.ShippingAddress.Street</section>
</article>

<article class="esh-orders-detail-items row">
<section class="esh-orders-detail-item col-xs-12">@Model.ShippingAddress.City</section>
</article>

<article class="esh-orders-detail-items row">
<section class="esh-orders-detail-item col-xs-12">@Model.ShippingAddress.Country</section>
</article>
</section>

<section class="esh-orders-detail-section">
<article class="esh-orders-detail-titles row">
<section class="esh-orders-detail-title col-xs-12">ORDER DETAILS</section>
</article>

@for (int i = 0; i < Model.OrderItems.Count; i++)
{
var item = Model.OrderItems[i];
<article class="esh-orders-detail-items esh-orders-detail-items--border row">
<section class="esh-orders-detail-item col-md-4 hidden-md-down">
<img class="esh-orders-detail-image" src="@item.PictureUrl">
</section>
<section class="esh-orders-detail-item esh-orders-detail-item--middle col-xs-3">@item.ProductName</section>
<section class="esh-orders-detail-item esh-orders-detail-item--middle col-xs-1">$ @item.UnitPrice.ToString("N2")</section>
<section class="esh-orders-detail-item esh-orders-detail-item--middle col-xs-1">@item.Units</section>
<section class="esh-orders-detail-item esh-orders-detail-item--middle col-xs-2">$ @Math.Round(item.Units * item.UnitPrice, 2).ToString("N2")</section>
</article>
}
</section>

<section class="esh-orders-detail-section esh-orders-detail-section--right">
<article class="esh-orders-detail-titles esh-basket-titles--clean row">
<section class="esh-orders-detail-title col-xs-9"></section>
<section class="esh-orders-detail-title col-xs-2">TOTAL</section>
</article>

<article class="esh-orders-detail-items row">
<section class="esh-orders-detail-item col-xs-9"></section>
<section class="esh-orders-detail-item esh-orders-detail-item--mark col-xs-2">$ @Model.Total.ToString("N2")</section>
</article>
</section>
</div>
</div>

Fichier .cshtml se traduisant en élément graphique par l’image ci-dessous :

order_details.PNG

Détail du produit Mug

Il en va de même pour toutes les pages, chaque pages fonctionnant de la même façon. Notre Contrôleur va gérer nos données (Modèle) et retourner la Vue correspondante au Contrôleur.

 

Conclusion

Ce qu’on peut tirer de ce projet, est l’application d’une architecture monolithique en opposition avec le eShopOnContainers qui se base sur les micro-services.

Ce sont deux approches différentes et qui ont chacune leurs avantages et leurs inconvénients.

Néanmoins ces derniers temps, les entreprises privilégient les micro-services avec Docker et le concept de conteneurisation.

Article écrit par :

  • Julie LACOGNATA <julie.lacognata@infeeny.com>
  • Kévin ANSARD <kevin.ansard@infeeny.com>

Chroniques Techniques sur le JDN

Retrouvez les 3 dernières chroniques de notre Leader de Domaine .NET (ChristopheP) sur le Journal du Net (JDN):

.NET 5 : introduction à la plateforme unifiée de Microsoft – A partir de novembre 2020, Microsoft fusionne .NET Framework et .NET Core 3.x pour créer .NET 5. Une plateforme unifiée pour les développeurs combinant dev .NET, Cloud, gaming, IoT, web, et l’IA.

Comment devenir un développeur expert Microsoft ? – Le monde du développement selon Microsoft est complexe. Il faut savoir décrypter les messages envoyés par le marketing du groupe pour pourvoir lire entre les lignes.

Comment j’ai démystifié Kubernetes, sous Linux Ubuntu – Kubernetes est la technologie cloud à la mode. On en parle partout. J’ai voulu m’intéresser à ça et maîtriser la chose en local avant de la mettre en oeuvre dans le cloud. Voici la synthèse de mon expérience.