________     _   _
                        |        |  _| |_| |_
                        |   _____| |_   _   _|
                        |  |        _| |_| |_
                        |  |_____  |_   _   _|
                        |        |   |_| |_| 
                        |________| 
                            Cours sur le c#



                         csharp.md  25/08/2023
                            ultimecool.com

Sommaire


CHAPITRE 1 - C# Généralités
     1.1. Visual Studio et Premier programme en C#
     1.2. Introduction au language objet:
     1.3. Première classe en C#
     1.4. Injection de dépendances
     1.5. Attributs statiques
     1.6. Assemblées (Assemblies)
     1.7. Tests unitaires
     1.8. Syntaxe structures et tableaux
     1.9. Passage de paramètres par référence
    1.10. Surdefinition des fonctions:
    1.11. Gestion des Evenements
    1.12. Gestion des exceptions
    1.13. Poo Inheritance
    1.14. Classes abstraite
    1.15. Interfaces
    1.16. Différences entre Classe abstraite et Interface
    1.17. Sur definition d'operateurs:
    1.18. Classes scéllés
    1.19. Les collections
    1.20. Documentation du code
    1.21. Trace et débug

CHAPITRE 2 - Interfaces notions avancées.
     2.1. Extensibilité avec les interfaces
     2.2. Implementation d'interface explicite
     2.3. RepositoryFactory
     2.4. La Couche View Model MVVM
     2.5. Chargement dynamique
     2.6. Dependency Injection
     2.7. Mocking
     2.8. Méthodes d'extension
     2.9. Signal-R

CHAPITRE 3 - LINQ Fundamentals
     3.1. Introduction
     3.2. Comparaison avec les tableau Array
     3.3. GroupJoin
     3.4. Select
     3.5. OrderBy
     3.6. GroupBy
     3.7. Having
     3.8. Linq-to-XML

CHAPITRE 4 - .Net Core
     4.1. C'est quoi Asp.Net Core
     4.2. Asp.Net Core au plus simple
     4.3. Asp.Net Core au plus simple API
     4.4. Asp Dot Net Core MVC avec Entity Framework
     4.5. EntityFramework
     4.6. Deployer une application ASP.NET
     4.7. Migrer de ASP.NET Core 2.2 à 3.0

CHAPITRE 5 - Fiches, Composants et ressources.
     5.1. Communication entre les controles
     5.2. Traitement asynchrone sur les controles
     5.3. Utilisation des evenement Background
     5.4. Utilisation des threads

CHAPITRE 6 - Gestionnaires de contenus CMS
     6.1. Orchard CMS

Introduction

Le langage C# est un langage intermédiaire entre le puissant mais compliqué C++ et le facile mais limité Visual Basic. Un fichier C# porte l'extension .CS.

C# est :

  • Souple : Un programme C# peut être exécuté sur la machine sur laquelle il se trouve ou bien transmis par l'intermédiaire du Web pour être exécuté sur un ordinateur distant.

  • Puissant : C# dispose essentiellement du même jeu d'instructions que C++, mais avec les angles arrondis.

  • Facile à utiliser: Dans C#, les commandes responsables de la plupart des erreurs ont été modifiées pour les rendre plus sure.

  • Visuel : La bibliothèque de C# fournit les outils pour créer directement des fenêtres d'affichage élaborées.

  • Prêt pour Internet : C# est le pivot de la nouvelle stratégie Internet de Microsoft, nommée .NET (prononcer point net).

  • Sûr: Tout langage destiné à une utilisation sur Internet doit contenir sous une forme ou sous une autre des outils de sécurité pour se protéger contre les hackers.

Enfin, C# est une partie intégrante de .NET.

Qu'est-ce que .NET ? . NET est la stratégie adoptée par Microsoft dans le but d'ouvrir le Web aux simples mortels comme vous et moi.
Il est difficile de programmer pour Internet dans des langages comme C ou C++. Sun Microsystems a répondu à ce problème en créant le langage Java. Celui-ci repose sur la syntaxe de C++, rendue un peu plus accessible, et est centré sur le principe d'un développement distribué.

CHAPITRE 1 - C# Généralités

1.1. Visual Studio et Premier programme en C#

Modifier l'environnement pour accélérer Visual Studio:

1) Paramétrer les exclusion de Windows Defender:

Exclure les processus:


C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\
- Web\External\node.exe
C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\
- IDE\devenv.exe
- ServiceHub\Hosts\ServiceHub.Host.Node.x86\ServiceHub.Host.Node.x86.exe
C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe
C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe
C:\Program Files\dotnet\dotnet.exe
C:\Program Files\nodejs\node.exe
sqlservr.exe, iisexpress.exe, iisexpresstray.exe

Exclure les dossiers:


// Dossiers des projets
D:\sources
// visual studio & tools
C:\Program Files (x86)\Microsoft Visual Studio 10.0
C:\Program Files (x86)\Microsoft Visual Studio 14.0
C:\Program Files (x86)\Microsoft Visual Studio
C:\Windows\assembly
C:\Windows\Microsoft.NET
C:\Program Files (x86)\MSBuild
C:\Program Files\dotnet
C:\Program Files (x86)\Microsoft SDKs
C:\Program Files\Microsoft SDKs
C:\Program Files (x86)\Common Files\Microsoft Shared\MSEnv
C:\Program Files (x86)\Microsoft Office
C:\Program Files (x86)\IIS Express\iisexpress.exe
C:\Program Files (x86)\IIS Express\iisexpresstray.exe

// cache folders
C:\ProgramData\Microsoft\VisualStudio\Packages
C:\Program Files (x86)\Microsoft SDKs\NuGetPackages
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files
C:\Users\USERNAME\AppData\Local\Microsoft\VisualStudio
C:\Users\USERNAME\AppData\Local\Microsoft\WebsiteCache
C:\Users\USERNAME\AppData\Local\Jetbrains
C:\Users\USERNAME\AppData\Roaming\Microsoft\VisualStudio
C:\Users\USERNAME\AppData\Roaming\JetBrains
C:\Users\USERNAME\AppData\Roaming\npm
C:\Users\USERNAME\AppData\Roaming\npm-cache

2) Configurer Visual Studio

Vous trouverez ci-dessous une liste de paramètres recommandés pour améliorer la réactivité de l'interface utilisateur et les performances de l'environnement, en fonction des meilleures pratiques.


Environment -> General
    Uncheck “Automatically adjust visual experience based on client performance”
    Uncheck “Enable rich client visual experience”
    Check “Use hardware graphics acceleration if available”

Environment -> AutoRecover
    Uncheck “Save AutoRecover information every”

Environment -> Documents
    Check “Save documents as Unicode when data cannot be saved in codepage”

Environment -> Startup
    Uncheck “Download content every”

Environment -> Synchronized settings
    Uncheck “Synchronize settings across devices when signed into Visual Studio”

Projects and Solutions
    Uncheck “Track active item in solution explorer”
    Check “Lightweight solution load for all solutions”

Projects and Solutions -> Build and Run
    Check “Only build startup projects and dependencies on run”

Projects and Solutions -> Web Package Management
    Set “Restore on Project Open” to false (for Bower)
    Set “Restore on Save” to false (for Bower)
    Set “Restore on Project Open” to false (for NPM)
    Set “Restore on Save” to false (for NPM)

Text Editor -> C# -> Advanced
    Uncheck “Enable full solution analysis”

Text Editor -> JavaScript/TypeScript -> EsLint
    Set “Enable ESLint” to false

Debugging
    Uncheck “Suppress JIT optimization on module load (Managed only)”
    Uncheck “Enable Edit and Continue”

Debugging -> Just-In-Time
    Uncheck “Script”

IntelliTrace
    Uncheck “Enable IntelliTrace”

Node.js Tools
    Set “Check for surveys/news” to “Never”

3) Désactiver les extensions

  • Developer Analytics Tools
  • Microsoft Office 365 API Tools

4) Charger tous les symboles

  • Allez dans Outils -> Options -> Débogage -> Symboles
  • Cliquez sur le bouton “…” et créez ou sélectionnez un nouveau dossier quelque, part sur votre ordinateur local pour stocker les symboles mis en cache.
  • Cliquez sur “Charger tous les symboles”, et attendez que les symboles soient téléchargés sur les serveurs de Microsoft, ce qui peut prendre un certain temps. Notez que le bouton Charger tous les symboles, est uniquement disponible lors du débogage.
  • Empêcher Visual Studio d’interroger à distance les serveurs Microsoft.

réf:
https://medium.com/burak-tasci/tweaking-the-environment-to-speed-up-visual-studio-79cd1920fed9

Deboguer avec Visual Studio:

Dans Visual Studio on peut déboguer le C# et le javascript en mettant des points d'arrêts. On peut les exporter et les importer selon les modules à déboguer. Attention les points d'arrêt qui s'étendent sur trop de lignes, peuvent bloquer l'importation. Les points d'arrêts sont paramétrables avec des étiquettes, des conditions d'arrêt et des traces. Il est ensuite facile de faire des recherches dessus, dans le champ de recherche du panneau "points d'arrêt" (icône >>). Cela permet de pouvoir supprimer ou activer les points selon leur étiquette(s).

Si un point d'arrêt n'est pas lié c'est soit :

  • qu'il est situé dans un autre projet que le projet de démarrage
  • que la cache du navigateur doit être vidé. Pour vider le cache, depuis chrome, aller sur l'écran correspondant, puis faire F12 et un click droit sur l'icône de rechargement, ensuite cliquer sur "Vider le cache et effectuer une actualisation forcée"
  • parfois il est nécessaire de "Régénérer la solution". Pour supprimer tous les fichiers binaires issues de la génération faire "Nettoyer la solution".

Pour déboguer :

  • F5 : pour démarrer le débogage
  • Shift + F5 : pour stopper le déboguage
  • F10 : pour effectuer un pas à pas principal
  • F11 : pour effectuer un pas à pas détaillé
  • Maj + F11 : pour effectuer un pas à pas sortant
  • Ctrl + G : pour aller à la ligne no

Erreurs de compilations avec Visual Studio

Les packages s'installent dans le sous dossier "packages". Et les dépendences sont inscrites dans le projet. Pour recréer le dossier "packages", (à la manière de npm install), il faut aller dans la Console du Gestionnaire de package, et taper "Update-Package". Cependant sur les gros projet je conseille de conserver ce dossier dans le cas ou certaines dépendence ne sont pas à jour.

Lors de la mise à jour des packages, si vous avez une erreur The length of the full path for the solution... exceeds the maximum path length Cette erreur est due à un bug du gestionnaire de packages dans Vis Stu 2017, qui cherche des références contenus dans des dossiers inutilisés. Aller dans Générer Nettoyer la solution. Cela supprimera tous les dossiers obj et bin.

Lors de la mise à jour des packages, si vous avez une erreur SSL update-package : Impossible de charger l'index de service pour la source
https://dotnet.myget.org/F/dotnet-core
.

aller sur https://www.ssllabs.com/ssltest pour vérifier la validité du package. pour dotnet.myget.org, il a été retiré fin 2020. Supprimer le fichier de config NuGet.config de la solution qui fait référence à dotnet.myget.org

Cela peut provenir d'un problème de proxy. Dans ce cas accédez à l'emplacement d'installation de Visual Studio C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE Ouvrez le fichier devenv.exe.config Ajouter defaultProxy tags comme ci-dessous, dans la balise system.net existante -


<system.net>
  <defaultProxy enabled="true" useDefaultCredentials="true">
      <proxy usesystemdefault="true" bypassonlocal="true" />
  </defaultProxy>
  <settings>
 

Lors de la compilation si vous avez une erreur de Dll manquande Quand c'est une DLL d'un de vos projet, il faut vérifier l'ordre de compilation des projets, Parfois ça fonctionne à la deuxième construction. Si ça bloque encore sur une dll manquante d'un de vos projet dont la construction ne s'est pas faite. Aller sur le projet dont la dll est manquante (Sélectionner le projet dans l'explorateur de solution, ou définir comme projet de demarrage), et faire Générer, Regénérer .

En cas d'erreur de syntaxe en rapport avec des changements de version de package Par exemple, un code source écrit avec Automapper v6.1.1, aura des erreurs de compilation avec la v10.0.0 : 'ISourceMemberConfigurationExpression' ne contient pas de définition pour 'Ignore'

Dans ce cas il faut bien vérifier la référence au niveau du projet, voir les propriétés de la référence à AutoMapper, notamment la version, et éventuellement supprimer la mauvaise dll dans l'emplaceent indiqué.

Premier programme en C

C# definit les caractères d'échappements suivants:


    \' – simple cote
    \" – double cote
    \\ – backslash
    \0 – Unicode character 0
    \a – Alert (character 7)
    \b – Backspace (character 8)
    \f – Form feed (character 12)
    \n – New line (character 10)
    \r – Carriage return (character 13)
    \t – Horizontal tab (character 9)
    \v – Vertical quote (character 11)
    \uxxxx – Unicode escape sequence for character with hex value xxxx
    \xn[n][n][n] – Unicode escape sequence for character with hex value nnnn
                  (variable length version of \uxxxx)
    \Uxxxxxxxx – Unicode escape sequence for character with hex value xxxxxxxx
                 (for generating surrogates)

using System;
public class Program {
        static void Main() {
          Console.WriteLine(DateTime.Now.DayOfWeek)

 

Compilation: C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc hollo.cs


using System;
public class Program {
  static void Main(string[] args) {
    if (args.Length > 0)
      Console.WriteLine("Hello, "+args[0]);
    else {
      Console.WriteLine("Name and Age");
      string name = Console.ReadLine();
      int age = int.Parse(Console.ReadLine());
      Console.WriteLine("Hello, "+name);
      if (age > 35)
        Console.WriteLine("How old !");
    }

 

Ajouter des paramétres d'exécution: Dans l'explorateur de solutions Click droit HelloWord Propriétés, Déboguer

Ajouter des paramétres d'exécution: Dans l'explorateur de solutions Click droit HelloWord Propriétés, Déboguer

1.2. Introduction au language objet:

Pourquoi la p.o.o (Programmation Oriente Objet)?

Pour la modularite: modules independant les uns des autres. Pour la maintenabilite et la reutilisabilite. L'objectif est de reutiliser le maximum d'objets existant.

attribut: ensemble de variables qui constituent les donnees de la classe. methode: ensemble des fonctions qui constituent les traitements de la classe classe: une classe est un type d'objet qui regroupe attributs et methodes. objet: un objet est l'integration d'une classe. instance: variable qui a été déclarée du type d'une classe. encapsulation: regroupement des donnees dans un objet pour masquer les attributs interphace: vue externe de l'objet (prototype des differentes methodes) accesseurs: methodes qui renvoient ou qui modifie la valeur d'un attribut.


  ex:  objet voiture
       donnees:     vitesse, couleur, marque, annee, ...
       traitement:  se deplacer, prendre de l'essence, s'user, ...

       voiture r5,r21,mustang;
   r5,r21 et mustang sont des instances de l'obet voiture.

cette methode permet d'éviter que des variables soient modifiées n' importe ou, et intempestivement.

ex: r5.annee=1976; // pas bon r5.setannee(1976); // bon: principe d'encapsulation Si l'attribut annee change de nom dans la classe, on aura pas a modifier tout le programme. L'encapsulation n'est pas obligatoire en c#, c'est au programmeur de faire attention.

En informatique et en théorie des types, le polymorphisme, est le concept consistant à fournir une interface unique à des entités pouvant avoir différents types.

L’opérateur is vérifie si le résultat d’une expression est compatible avec un type donné, ou (à compter de C# 7. 0) teste une expression par rapport à un modèle. À compter de C# 7. 0, les instructions is et switch prennent en charge les critères spéciaux. Le mot clé is prend en charge les modèles suivants :

  • Modèle de type, qui teste si une expression peut être convertie en un type spécifié et, si possible, effectue un cast de la variable en une variable de ce type.

    using System;
    public class Employee : IComparable  {
      public String Name { get; set; }
      public int Id { get; set; }

      public int CompareTo(Object o)  {
        if (o is Employee e)  {
          return Name.CompareTo(e.Name);
        }
        throw new ArgumentException(
        "o is not an Employee object.");
    }}
 

 
 
 
 
 
public int CompareTo(Object o)  {      
  var e = o as Employee;              
  if (e == null)  {                    
    throw new ArgumentException(      
    "o is not an Employee object.");
}                                    
return Name.CompareTo(e.Name);
 
  • Modèle de constante : teste si une expression correspond à une valeur de constante spécifiée. Ex: if (o is null)

  • Modèle de variable : correspondance qui réussit toujours et lie la valeur d’une expression à une variable locale.


    int[] testSet = { 100271, 234335, 342439, 999683 };
    var primes = testSet.Where(n => Factor(n).ToList() is var factors
     && factors.Count == 2 && factors.Contains(1) && factors.Contains(n));
 

1.3. Première classe en C#

Ajouter une classe: Dans l'explorateur de solutions Click droit Grades Ajouter, Classe... Sélectionner classe et nomer la classe. Pour créer une constructor automatiquement taper ctor et double Tab Pour créer un console.write automatiquement taper le snippet cw et double Tab


namespace Grades
{
  class GradeBook {
    public GradeBook() {
    }
    public void AddGrade(float grade) {
      grades.Add(grade);
    }
    public List<float> grades = new List<float>();
  }
}

namespace Grades
{
  class Program {
    static void Main(string[] args) {
      GradeBook book;
      book = new GradeBook();
      book.AddGrade(91.5f);
      book = new GradeBook();
      book.AddGrade(75);
    }
  }
}
 

La variable book contient une adresse vers l'emplacement mémoire ou est stockée l'instance de objet/classe de type GradeBook. Elle peut pointer à tout objet de GradeBook dont je dispose, ainsi lors de la 2eme affectation, l'accès à la 1ère instance est perdue. L'instance perdue est détruite par le garbage collector.

Le mot clé public, devant la déclaration de la liste de grades, permet un accès direct à cette variable sans passer par la méthode ex: book.grade.Add(87); Avec private la variable grade est encapsulée dans la classe; elle ne sera accéssible qu'au travers du modifier AddGrade ex: book.AddGrade(87);

Le parcours d'une list se fait par foreach(float grade in grades)

En c# les classes systèmes possèdent des méthodes d'aide, par ex, Float possède la méthode MaxValue ex: Float.MaxValue. Les méthodes d'aide ne modifient pas la valeur de la variable. Ex: name = name.ToUpper(); et nom name.ToUpper(); La touche F12 permet d'ouvrir la source de la classe qui est sous le curseur.

1.4. Injection de dépendances

Lorsque deux classes entrent en interaction, un couplage se crée. Il existe une technique permettant de limiter l’impact de ce couplage sur la maintenabilité du code. Il s’agit de l’injection de dépendance.

L’injection de dépendance permet d’établir de façon dynamique une relation entre les classes. Elle permet ainsi de découpler les éléments les uns des autres et de ne plus décrire explicitement leurs relations dans le code. De ce fait, elle facilite la modification ultérieure du code, et l’ajout de nouvelles fonction- nalités.


public class Car  {
    private ISteeringWheel steeringWheel;

    public Car(ISteeringWheel steeringWheel)  {
        this.steeringWheel = steeringWheel;
    }
   
    public void TurnLeft()  {
        this.steeringWheel.Rotate("left");

 

La classe Car ne reçoit plus l’objet spécifique steeringWheel mais un objet respectant le contrat (interface) ISteeringWheel (qui implémente les contraintes d’un volant). Dorénavant, quand notre voiture aura besoin d’un volant qui contient un klaxon, il suffira de ne plus lui donner l’objet steeringWheel mais l’ objet steeringWheelwithklaxon. Le comportement du volant reste inchangé pour la voiture. Quant au klaxon, il ne sait pas que le bouton le déclenchant est aussi un volant. Dans le découplage des éléments, les objets ne doivent pas savoir avec qui ils interagissent, quelles sont leurs caractéristiques spécifiques respectives, ni même qui les appelle.

steeringWheelWithKlaxon peut donc respecter les contrats ISteeringWheel (précédemment défini) ainsi que IKlaxon:

1.5. Attributs statiques:

Lors de chaque création d'une instance, les attributs, et les pointeurs sur les méthodes sont recopiées en mémoire. Ainsi chacune des instance contient ses propres attributs. Le fait de déclarer un attribut 'statique' peut permettre à toutes les instances de partager la meme donnée. La donnée peut être lue sans instancier la classe ex: GradeBook.MaximumGrade


  ex:   class point  {                                 point.nb_instances=0;
            private int x,y;              
            int x,y;                      
            static int nb_instances;      
            public void point(int a, int b)  {
              x=a; y=b;
              nb_instances++;
              Console.WriteLine("inst: "+nb_instances);
            }
        }
 

1.6. Assemblées (Assemblies)

Pour Ajouter une référence, clic droit sur Références dans l'explorateur de solutions, Ajouter une référence. Vous pouvez faire une rechercher sur le nom. ex: SpeechSynthesizer. La référence s'ajoute à la liste de références.

L'explorateur d'objets permet de voir les classes système, avec leur méthodes. Pour afficher une référence dans l'explorateur d'objets, click droit, Afficher dans l'explorateur d'objets. On voit la liste des classes importable avec using, ainsi que les méthodes utilisables.


using System.Speech.Synthesis;
SpeechSynthesizer synth = new SpeechSynthesizer();
synth.Speak("Bonjour mon beau");
 

1.7. Tests unitaires

Consiste à écrire un test pour tester un code. Click droit dans l'explorateur de solutions, sur le projet en cours, Ajouter, Nouveau projet, Test, Application de tests unitaires. Nommer le projet ex: Grades.Tests. Ajouter une commande de test dans TestMethod1, ex: Assert.AreEqual(3, 3); Maintenant, dans le menu Test de VisualStudio, il est possible d'exécuter tous les tests.

Supprimer l'unité générée UnitTest1, et recréer une classe ex: "GradeBookTests" Taper [TestClass] au dessus de la classe, correction éventuelles, cliquer sur Microsoft.VisualStudio.TestTools.UnitTesting.

Ajouter une Assemblée dans les références du projet de test, choisir projets, solution, Nommer l'unitée Grades. Au besoin utiliser l'attribut "internal" au lieu de "private" pour pouvoir accéder à la propriété de la classe depuis la classe de test. Ajouter "public" devant class GradeBook et GradeBookTests.


using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Grades.Tests {
    [TestClass]
    public class GradeBookTests  {
        [TestMethod]
        public void ComputesHighestGrade()  {
            GradeBook book = new Grades.GradeBook();
            book.AddGrade(10);
            book.AddGrade(90);
            Assert.AreEqual(90, book.grades.Max(), 0.01);
}}}
[TestMethod] peut être écrit en tapant le snippet testm Tab Tab
 

1.8. Syntaxe structures et tableaux

Accesseurs:


private string _name;
public string Name {
  get { return _name; }
  set { if (!String.IsNullOrEmpty(value)) _name = value; }
}

public enum PayrollType { Contractor = 1, Salaried, Executive, Hourly }
 

Iteration:


int [] scores = new int[4];
int [] ages = {2, 21, 40};
foreach(int score in scores)
  totScore += score;
fct(int [] scores)

for (int i = 0; i < age; i++) { }
while (age > 0) { age -= 1; }
do { age++; } while (age < 100);
 

Condition:


if {} else {}
(a > b) ? a:b;

switch(name) {
 case "Scott": ServeSoda(); break;
 case "Scotz": ServeSoda(); break;
 default: ServeMilk(); break;
}

sauts: break, continue goto (goto skip skip:) return throw
 

1.9. Passage de paramètres par référence

Cela se fait par le mot clé ref


void GiveBookName(ref GradeBook book)
{
  book = new GradeBook();
  book.name = "A GradeBook";
}
GradeBook book2 = new GradeBook();
GiveBookName(ref book2);
Assert.AreEqual("A GradeBook", book2.Name);
 

Le mot clé out entraîne le passage des arguments par référence. Il fait du paramètre formel un alias de l’argument, qui doit être une variable. En d’autres termes, toute opération portant sur le paramètre est effectuée sur l’argument. Il est similaire au mot clé ref, à la différence que ref nécessite que la variable soit initialisée avant d’être passée. Il est également similaire au mot clé in, à la différence que in n’autorise pas la méthode appelée à modifier la valeur d’argument. Pour utiliser un paramètre out,


  int initializeInMethod;
  OutArgExample(out initializeInMethod);
  Console.WriteLine(initializeInMethod);     // value is now 44
  void OutArgExample(out int number)  { number = 44; }

  var isNumeric = int.TryParse(celcusDegree, out n); // n reçoit la valeur
 

les éléments ignorés, qui sont des variables d’espace réservé qui sont intentionnellement inutilisées dans le code de l’application. Les éléments ignorés sont équivalents à des variables non affectées : elles n’ont pas de valeur. Les éléments ignorés sont particulièrement utiles pour travailler avec des tuples quand le code de votre application utilise certains éléments d’un tuple mais ignore les autres. ex :


  public class Example
  {
    public static void Main()  {
      var (_, _, _, pop1, _, pop2) =
        QueryCityDataForYears("New York City", 1960, 2010);
      // Affiche:Population change, 1960 to 2010: 393,149
      Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
    }

    private static (string, double, int, int, int, int) QueryCityDataForYears(
      string name, int year1, int year2)
    {
      int population1 = 0, population2 = 0;
      double area = 0;
      if (name == "New York City")  {
        area = 468.48;
        if (year1 == 1960) population1 = 7781984;
        if (year2 == 2010) population2 = 8175133;
        return (name, area, year1, population1, year2, population2);
      }
      return ("", 0, 0, 0, 0, 0);
    }
  }
 

1.10. Surdefinition des fonctions:

Le compilateur fait la difference entre fonctions, par le nom, le nombre d'arguments, les types des arguments. Le type retourne n'est pas pris en compte.


  ex:   int fct (float value);
        int fct (int value);
        int fct (double value);
        int fct ();        // fct;  fct();
        int fct(params float[] values)
 

S'il existe une fonction surdefinite sans parametres, l'appel de cette fonction peut se faire de 2 maniere distinctes; avec ou sans parentheses. Dans ce cas, il ne doit pas y avoir une fonction avec que des parametres par defaut, car la distinction n'est pas possible. Il est possible de caster (modifier le type) les paramètre avec cette syntaxe: fct((int)aa);

1.11. Gestion des Evenements

Un delegate est un type référence qui peut être utilisé pour encapsuler une méthode anonyme ou nommée. Les délégués sont comparables aux pointeurs fonction en C++, mais ils offrent l’avantage d’être sûrs et de type sécurisé. Les délégués sont la base des événements.


public delegate void Writer(string message);
public class Logger {
  public void WriteMessage(String message) {
    Console.WriteLine(message);
  }
}
Logger logger = new Logger();
Writer writer = new Writer(logger.WriteMessage);
writer("Success!!");
 

Depuis visual studio, créer une classe NameChangedDelegate,

NameChangedDelegate


namespace Grades {
  public delegate void NameChangedDelegate(string existingName, string newName);
}
 

GradeBook.cs


public string Name
{
  get { return _name; }
  set {
      if (!String.IsNullOrEmpty(value))
      if(_name != value) {
          NameChanged(_name, value);
          _name = value;
      }
  }
}
public NameChangedDelegate NameChanged;
private string _name;
 

program.cs


namespace Grades  {
  class Program  {
    static void Main(string[] args)  {
      GradeBook book;
      book = new GradeBook();
      book.NameChanged += new NameChangedDelegate(OnNameChanged);
      book.NameChanged += new NameChangedDelegate(OnNameChanged);
      book.NameChanged += new NameChangedDelegate(OnNameChanged2);
      book.NameChanged -= OnNameChanged2;
      // book.NameChanged = null;
      book.Name = "sss";
      Console.Read();
    }
    static void OnNameChanged(string existingName, string newName)  {
      Console.WriteLine($"name change from {existingName} to {newName}");  }
    static void OnNameChanged2(string existingName, string newName)  {
      Console.WriteLine($"name change from {existingName} to {newName}");  }

 

Adaptation pour une classe Args :

NameChangedDelegateArgs


public class NameChangedEventArgs : EventArgs
{
  public String ExistingName { get; set; }
  public string NewName { get; set;  }
}
 

NameChangedDelegate


public delegate void NameChangedDelegate(Object sender,
  NameChangedEventArgs args);
 

GradeBook.cs


      if(_name != value) {
          NameChangedEventArgs args = new NameChangedEventArgs();
          args.ExistingName = _name;
          args.NewName = value;
          NameChanged(this, args);
          _name = value;
      }
 

program.cs


static void OnNameChanged(Object sender, NameChangedEventArgs args)  {
  Console.WriteLine($"name change from {args.ExistingName} to {args.NewName}");
}
 

utilisation de EventHandler (Delegate Inference)


public event EventHandler<NameChangedEventArgs> NameChanged;
protected virtual void OnNameChanged(string existingName, string newName)  {
  var del = NameChanged as EventHandler<NameChangedEventArgs>;
  if (del != null)
    del(this, new NameChangedEventArgs(_name, value));
}
book = new GradeBook();
book.NameChanged += new EventHandler<NameChangedEventArgs>(_workPerformed);
void _workPerformed(object sender, NameChangedEventArgs e) {}
ou avec une methode ananyme:
book.NameChanged += delegate(object sender, NameChangedEventArgs e) {
};
SubmitButton.Click += (s,e) => MessageBox.Show("Button");
delegate int AddDelegate(int a, int b);
static void Main(string[] args) {
  AddDelegate ad = (a, b) => a + b;
  int result = ad(1, 1); // result = 2
}
 

utilisation des delegates Action et Func


void ProcessAction(int x, int y, Action<int, int> action)
{
  action(x, y);
}
Action<int, int> myAction = (x, y) => Console.WriteLine(x + y);
ProcessAction(3, 2, myAction);
void ProcessFunc(int x, int y, Func<int, int, int> del)
{
  var result = del (x, y);
  Console.WriteLine(result);
}
Func<int, int, int> funcAddDel = (x, y) => x + y;
ProcessFunc(3, 2, funcAddDel);
 

1.12. Gestion des exceptions


try {
  throw new Exception();
  throw new ArgumentException("not a legal value");
}
catch (DivideByZeroException dbzEx) { }
catch (Exception ex){
  throw; // propage au niveau superieur
}

try {} finally {}
 

Si throw est exécuté dans un bloc try (depuis une fonction), les clauses catch associées à ce bloc sont examinées pour voir si l'une d'elles peut gérer l'exception. Si une clause catch est détectée, l'exception est gérée. Si aucune clause catch n'est détectée, la recherche se propage dans les bloc try-catch de niveau supérieur. Si aucun gestionnaire n'est trouvé, le programme appelle la fonction terminate() définie dans la bibliothèque du C++ standard. Cette fonction appelle abort() qui indique "Abnormal program termination".

1.13. Poo Inheritance

Inheritance:


public class A  {
  public void DoWork() {}
}
public class B : A  {} // La classe B hérite de la classe A
public class C : B  {} // La classe C hérite de la classe B
ex: public class NameChangedEventArgs : EventArgs
 

Polymorphism:

virtual: methode de la classe de base pouvant être surdéfinie.


//ThrowAwayGradeBook
namespace Grades
{
  public class ThrowAwayGradeBook : GradeBook  {
    public override GradeStatistics ComputeStatistics()  {
      return base.ComputeStatistics();
}}}
// GradeBook.cs
  public virtual GradeStatistics ComputeStatistics()  {
  }
 

1.14. Classes abstraite

Une classe abstraite, est une méthode qui ne peut pas être instanciée. Une méthode abstraite, est une méthode qui doit être obligatoirement surchargée. Elle se déclare avec Abstract. Ainsi il est possible de travailler sur tous les types de sous classes, depuis le type classe de base, au travers des méthodes abstraites qui sont forcément existantes. Les méthodes virtual, traitent le type spécifique de la la sous classe ex: Chien : Animal, Chien.cours Chat.cours List aAnimals chiens et chats, on peut faire courrir tous les animaux de la liste.


public abstract class Windows {
  public virtual string Title { get; set; }
  public virtual void Draw() { }
  public abstract void Open();
}
 

1.15. Interfaces

Une interface peut être instanciée, elles sont plus flexibles que les classes abstraites. C# permet de mettre en oeuvre plusieurs interfaces sur un type.


public interface IWindow  {
  string Title { get; set; }
  void Draw();
  void Open();
}

ex: foreach (float grade in book) {}
internal interface IGradeTracker : IEnumerable
public class GradeBook : GradeTracker  {
  public override IEnumerator GetEnumerator()  {
    return grades.GetEnumerator();
  }
}
 

Tous les objets .NET de l'interface IEquatable, contiennent une méthode Equals qui peut être surdéfinie. Equals prend en paramètre un type Object, qui n'est pas fortement typé. Pour surdéfinir Equals avec un type plus sure, il faut utiliser IEquatable Le snippet propfull et double Tab permet de générer une propriété.

Par convention, une classe concrete est dérivée d'une interface, et possède les propriétés et méthodes concrettes (ex: rectangle carré qui dérivent de polygon). Par convention le nom d'une interface commence par un "I" ex: ConcreteRegularPolygon -> IRegularPolygon

Par le principe de ségrégation, seules les interfaces utilisées par le client sont implémentées. L'héritage d'interface permet d'ajouter des fonctionnalités à une interface existante sans casser le code. ex: public interface ISaveable { string Save(); } public interface INamedSaveable : ISaveable { string Save(string name); }

1.16. Différences entre Classe abstraite et Interface

La différence principale entre les interfaces, et les classes abstraites est que certaines méthodes de la classe abstraite sont obligatoirements surchargées, alors que dans une interface elles ne le sont pas forcément (cas des méthodes définient en virtual dans l'interface).

Les classes abstraites peuvent contenir du code de mise en oeuvre, alors les Interface ne le peuvent pas. En C#, nous avons un concept d'héritage unique, une classe ne peut hériter que d'une seule classe de base. Inversement, une classe peut implémenter n'importe quel nombre d'interfaces.

Les membres des classes abstraites peuvent avoir des access modifiers (public protected internal). Les membres pour les interfaces sont automatiquement public. Par défaut toutes les classes héritent de la classe Object.

Classes Abstraites : Fiels, Properties, Constructors, Method, Events, Indexers Interfaces : Properties, Methods, Events, Indexers

Bonne pratique: Programmer pour une abstraction, pas pour un type concret.

Maintenabilité: Les interfaces sont moins senssibles aux changements, car on ne soucis pas du détail d'un objet mais uniquement d'une ou plusieurs propriétés, ou d'un type de donnée ex List IEnumerable, une modification d'une sortie de Array en List dans l'interface ne chanche pas l'utilisation via IEnumerable.

Classes abstraites avec une implémentation partagée: Les classes MembershipProvider et RoleProvider dans ASP.NET sont utilisés pour gérer la sécurité.

Il existe un grand nombre d'interfaces qui sont conçus pour ajouter des morceaux de fonctionnalité à des classes existantes:

  • IDisposable manage and free ressources
  • INotifyPropertyChanged dataBinding
  • IEquatable, IComparable Manage Equality
  • IObservable subscribe
  • IQueriable, IEnumerable

Base Classes qui implémentent une classe abstraite (car le code très différents)

  • SqlMembershipProvider
  • SqlConnection, OdbcConnection, EntityConnection List, ObservableCollection

1.17. Sur definition d'operateurs:

Il est possible de redefinir les operateurs =, +, -, *, /, == pour une classe. Il est importatnt dans ce cas, de redéfinir à l'identique Equals et ==, pour ce même type. Pour comparrer une référence il vaux mieux utiliser ReferenceEquals. De même il faut redéfinir != NotEquals et GetHashCode En effet == sur un objet retourne toujours une référence, mais peut être sur définie dans d'autres classes. La sur définition d'opérateur n'est pas possible avec un type générique T ex: static void Display(T x, T y) {} static void Display(T x, T y) where T: class {} permet l'opérateur ==


class MyType : IEquatable<MyType> {
  public bool Equals(MyType other) { return other.Name == this.Name; }
  public override bool Equals(object obj)  {
    if (obj == null) return false;
    if (obj.GetType() != this.GetType()) return false;
    return Equals((MyType)obj);
  }
  public static bool operator == (MyType lhs, MyType rhs) {
    return lhs.Equals(rhs);
  }
  public static bool operator != (MyType lhs, MyType rhs) {
    return !lhs.Equals(rhs);
  }
  override int GetHashCode()  {
    return this.Name.GetHashCode() ^ this.LastName.GetHashCode(); // ^ xor
  }
}
 

Dans une sous classe qui se distingue par une méthode supplémentaire:


public override bool Equals(object obj)  {
  if (!base.Equals(obj)) return false;
  MySousType rhs = (MySousType)obj;
  return this._methode == rhs._methode;
}
public override int GetHashCode()
{
  return base.GetHashCode() ^ this._methode;
}

IEqualityComparer<T>
 

1.18. Classes scéllés

Il est possible d'empêcher une classe d'être dérivée, grâce au mot-clé sealed. Pour masquer une méthode (ou une variable), on utilisera le mot-clé new devant cette méthode (ou variable). Il est préférable d'utiliser les interfaces IEquatable, IComparable, et IComparer dans une classe scéllé. En effet la classe de base ne connais pas les propriétés supplémentaires des classes dérivés. et ignoreras les champs supplémentaires dans les types dérivés.

Les comparer permettent de créer plusieurs compareurs (classes) pour un Type, il en existe plusieurs: IComparer, IEqualityCompareur IComparer permet le faire un sort() sur un tableau array ex:


Array.sort(list, new FoodNameComparer());
Array.sort(list, FoodNameComparer.Instance);

class FoodNameComparer : Comparer<Food>  {
  private static FoodNameComparer _instance = new FoodNameComparer();
  public static FoodNameComparer Instance { get { return _instance; } }
  public override int Compare(Food x, Food y)  {
    if (x==null && y==null) return 0;
    if (x==null) return -1;
    if (y==null) return 1;
    return string.Compare(x.Name, y.Name, StringComparison.CurrentCulture);

 

Un hashSet est une liste d'éléments uniques. A chaque addition dans la liste, le HashSet test l'égalité. Il faut créer une classe :EqualityCompareur avec une méthode override int GetHashCode si on veut modifier l'égalitée naturelle. var foodItems = new HashSet(FoodItemEqualityComparer.Instance); var names = new HashSet(StringComparer.CurrentCultureIgnoreCase); ex: return StringComparer.OrdinalIgnoreCase.GetHahCode(obj.Name) ^ ...


string[] arr1 = { "apple", "orange" };
string[] arr2 = { "apple", "orange" };
var arrEq = {IStructuralEquatable) arr1;
bool structEqual = arrEq.Equals(arr2, StringComparer.Ordinal);
var arrComp = {IStructuralComparable) arr1;
int structComp = arrComp.CompareTo(arr2, StringComparer.OrdinalIgnoreCase);
 

1.19. Les collections

3 familles de collections: Lists, Dictionaries, Sets Les dictionnaires vous permettre d'utiliser n'importe quel type pour la clé, des entiers, des dates, des chaines, ou des classes personnalisées. Les Sets permettent de faire une intersection ou un DISTINCT UNION en SQL entre deux collections, comme les dictionnaires ils sont basé sur une table de hachage mais n'ont pas de clé. Ils ne sont pas destinés a effectuer une recherche mais de fournir l'ensemble des enumérations.

C# implémente beaucoup de classes de collection :

  • ReadOnlyCollection
  • SortedList (bineryseach list ok keys modif slow)
  • LinkedList (liste non indexée à insertion rapide, car les elemnets sont liées les uns aux autres). LinkekListNode pour ajoutter des éléments.
  • Stack (last-in, first-out list) Queue (first-in, first-out list)
  • ObservableCollection (évenement à chaque modifications)
  • HashSet (élements uniques garanti)
  • List (liste indexée d'objets)
  • SortedSet
  • Array Array.GetLength(1) multidimentionel, Length longeur total multi Rank dimentions, getLowerBound(0) et GetUpperBound(0)
  • Collection (implémentation de IList pouvant être modifié)
  • Dictionary (liste avec une clé de type php) foreach (var pm in class) Console.WriteLine(pm.Key + pm.Value);
  • SortedDictionary (bineryseach list ok keys modif fast)
  • KeyedCollection .Net 2.0 2005 Generics List using System.Collections.Generic/ObjectModel; .Net 3.0 2007 LINK .Net 4.0 2010 Concurrent collections Multi-threaded support .Net 4.5 2012 Readonly contrats IReadOnlyList, IReadOnlyCollection Immutable collections via NUGET package parcours d'un tableau : string[] daysOfWeek = { "monday", "tuesday" }; foreach (string day in daysOfWeek) { Console.WriteLine(day); }

comparateur personnalisé:


var primeMinisters = new Dictionary<string, PrimeMinister>
  (StringComparer.InvariantCultureIgnoreCase)

var primeMinisters = new Dictionary<string, PrimeMinister>
  (new UncassedStringEqualityComparer())
{
  {"MT", new PrimeMinister("Margaret Thatcher", 1979)}
  {"TB", new PrimeMinister("Tony Blair", 1997)}
}

var primeMinisters = new SortedList<string, PrimeMinister>
  (new UncasedStringComparer())
{
  {"MT", new PrimeMinister("Margaret Thatcher", 1979)}
  {"TB", new PrimeMinister("Tony Blair", 1997)}
}

class UncassedStringEqualityComparer : IEqualityComparer<string>
{
  public bool Equals(string x, string y)  {
    return x.ToUpper() == y.ToUpper();  }
  public int GetHashCode(string obj)  {
    return obj.ToUpper().GetHashCode();  }
}
class UncasedStringComparer : IComparer<string>
{
  public int Compare(string x, string y)  {
    return string.Compare(x, y, StringComparison.InvariantCultureIgnoreCase);
  }
}
 

StringBuilder permet de concatener des chaines de caractère de façon très rapide Mais attention a la consommation de mémoire. En cas d'utilisation répété et avec beaucoup de données il faudra réutliser le stringBuilder.


StringBuilder sb = new StringBuilder();
sb.Append(); sb.ToString;

  [ThreadStatic]
  private static StringBuilder cachedStringBuilder;

  private static StringBuilder AcquireBuilder()
  {
      StringBuilder result = cachedStringBuilder;
      if (result == null)
      {
          return new StringBuilder();
      }
      result.Clear();
      cachedStringBuilder = null;
      return result;
  }

  private static string GetStringAndReleaseBuilder(StringBuilder sb)
  {
      string result = sb.ToString();
      cachedStringBuilder = sb;
      return result;
  }
  ...
  StringBuilder sb = AcquireBuilder();
  return GetStringAndReleaseBuilder(sb)
 

réf: StringBuilder with Caching
https://stackoverflow.com/questions/45968920/stringbuilder-with-caching-threadstatic

1.20. Documentation du code

Avant tout, il est possible d'installer le SDK .Net, pour avoir la doc des APIs, les exemples (ex pour signalR), et pour accéder au code source. Pour cela il faut d'abord installer dotnet-sdk-2.1.3-win-x64.exe, puis dans Visual Studio, il faut cloner

Les documentations .Net sont open source. Elles sont crées au format markdown. Le format markdown permet également de générer des fichier HTML, CHM ou pdf.

Toutes les documentations
https://docs.microsoft.com/fr-fr/documentation/

.Net (source Markdown, site, pdf):
https://github.com/dotnet/docs.fr-fr

https://docs.microsoft.com/fr-fr/dotnet/core/tools/project-json-to-csproj

https://docs.microsoft.com/fr-fr/dotnet/opbuildpdf/fundamentals/toc.pdf?branch=live

C Sharp (source Markdown, site, pdf):
https://github.com/dotnet/docs.fr-fr/tree/live/docs/csharp

https://docs.microsoft.com/fr-fr/dotnet/csharp/

https://docs.microsoft.com/fr-fr/dotnet/opbuildpdf/csharp/toc.pdf?branch=live

.Net Core (source Markdown, site, pdf):
https://github.com/dotnet/AspNetCore.Docs.fr-fr

https://docs.microsoft.com/fr-fr/aspnet/core/blazor/?view=aspnetcore-5.0

https://docs.microsoft.com/fr-fr/dotnet/opbuildpdf/csharp/toc.pdf?branch=live

Entity Framework (source Markdown, site, pdf):
https://github.com/dotnet/EntityFramework.Docs.fr-fr

https://docs.microsoft.com/fr-fr/ef/efcore-and-ef6/

https://docs.microsoft.com/fr-fr/ef/opbuildpdf/toc.pdf?branch=live

Télécharger le fichier docfx.zip depuis https://github.com/dotnet/docfx/releases
et le décompresser dans "C:\Program Files\docFx". Ajouter le chemin dans le PATH pour pouvoir l'exécuter n'importe où. Pour générer la documentation en html dans le sous dossier "_site", éxécuter la commande suivante:

C:\> docfx aspnetcore\docfx.json --serve

Ensuite pour générer la documentation en chm le mieux est d'utiliser WinCHM Pro. Aller dans le dossier "_site\styles", et supprimer tous les fichiers ".js". Faire nouveau projet, Create a project using existing html files et sélectionner le dossier "_site". WinCHM vat générer le sommaire à partir des pages. Il est possible de le modifier, par exemple de déplacer les dossiers à la racine, etc. En cliquant sur un élément du sommaire, dans l'onglet "Topic options", on peut ajouter des mots clés pour la création d'un index.

Dans Projet, "Propriétés de ...", Build, cocher "Fichier de documentation XML". Utiliser &gt; et &lt; pour mettre les caractères > et < .

Ces tags sont utilisables en fonction de l'entité documentée

Entité

Tags utilisables


class
struct
interface
delegate
enum
event
constructor
property
method


<summary>, <remarks>, <seealso>
<summary>, <remarks>, <seealso>
<summary>, <remarks>, <seealso>
<summary>, <remarks>, <seealso>, <param>, <returns>
<summary>, <remarks>, <seealso>
<summary>, <remarks>, <seealso>
<summary>, <remarks>, <seealso>, <param>, <permission>, <exception>
<summary>, <remarks>, <seealso>, <value>, <permission>, <exception>
<summary>, <remarks>, <seealso>, <param>, <returns>, <permission>,
<exception>

Description des Tags:

Tag

Description


<c>
<code>
<example>
<exception>
<list>
<para>

affiche le texte qu'il contient sous la forme de code.
affiche le texte sous la forme de code en multiligne.
fournit un exemple d'utilisation de l'entité.
fournit des infos sur la levée d'une exception par une méthode.
crée une liste d'élément avec puce, numéroté ou en tableau.
crée un paragraphe à l'intérieur d'un tag.

Exemple de description:


/// <summary>
/// Converts a CSV string array to a Json array format.
/// </summary>
/// <example>
/// <code>
/// List&lt;CsvLineType&gt; lt = new List&lt;CsvLineType&gt;();
/// </code>
/// </example>
/// <param name="headerLine">Keep blank if</param>
/// <param name="typeFieldCol">Column number</param>
/// <param name="types">List of CsvLineType</param>
/// <returns>json value</returns>
 

Génération de la documentation au format Web

GhostDoc est une extension gratuite pour Visual Studio. Il permet de générer la documentation complète de l'ensemble de la solution. Cependant il produit un chm incomplet. Mais il est possible de reconstituer un document valide à partir des fichiers temporaires (%TEMP%\SubMain.GhostDoc`1). Voici la procédure:

Dans Visual Studio, faire Outils, GhostDog, Build Help Documentation

Installer HTMLhelp workshop lancer hh -decompile Destdir Source.chm sur le chm généré

Récupérer les fichiers manquant depuis le dossier temporaire suivant: %TEMP%\SubMain.GhostDoc`1

.\icons\ .\scripts\ .\Styles\ xxx.hhp

Créer un fichier .hhp pour qu'il corresponde aux fichiers générés .hhc et .hhk

Source.hhp :


[OPTIONS]
Compatibility=1.1 or later
Compiled file=Source.chm
Contents file=Source.hhc
Default topic=index.htm
Display compile progress=No
Index file=Source.hhk
Language=0x40c Français (France)
[INFOTYPES]
 

Lancer htmlHelpWorkshop et ouvrir le projet Source.hhp et faire: File -> Compile Un nouveau fichier chm est généré. Il est possible de l'ouvrir avec lhelp.exe

Il est possible d'automatiser la génération avec hhc.exe contenu dans le dossier d'installation de HTMLhelp workshop "%ProgramFiles(x86)%\HTML Help Workshop\hhc" Source.hhp

réf: documentation du code en c#
https://www.jmdoudoux.fr/microsoft/doctechcsharp/doctechcsharp.htm

1.21. Trace et débug


using System.Diagnostics; classes Debug ou Trace
Debug.WriteLine("Debug Information-Product Starting "); ou
Trace.WriteLineIf(iUnitQty > 50, "This message WILL appear");
 

Ces closses implémentent les méthodes suivantes: WriteLine, WriteLineIf, Indent, Unindent, Assert, Flush

Lors de l'execution en mode débug, on peut afficher les outils de diagnostic qui donneront la consommation mémoire et processeur du processus. Menu Déboguers, Fenêtres, Afficher les Outils de Diagnostic.

Accès à IIS Express depuis l’extérieur

première chose : lancez votre projet et regardez l’URL, où de vous rendre dans les propriétés de votre projet onglet « Web » où vous trouverez l’URL du projet avec son port. Fermez Visual Studio et vérifiez que IIS Express ne s’exécute plus.

Dans le pare-feu de Windows (si ce dernier est actif) ajouter une règle entrante autorisant le port du site web. Vous pouvez également dans un invité de commande en mode administrateur, exécuter la commande (sur une seule ligne):


  netsh advfirewall firewall add rule name="IISExpressWeb" dir=in protocol=tcp
    localport=55713 action=allow
 

Autoriser l'écoute de votre adresse IP et port


netsh http add urlacl url=http://192.168.1.1:55713/ "user=Tout le monde"
 

Attention à bien saisir l’adresse IP avec le port. De même pour la partie «user» il s’agit du groupe autorisé, en anglais le groupe se nomme « Everyone »

Modifier le fichier « $(solutiondir).vs\config\applicationhost.config » pour avoir ceci :


<binding protocol="http" bindingInformation="*:55713:localhost" />
<binding protocol="http" bindingInformation="*:55713:192.168.1.1" />
 

A partir de maintenant IIS Express et donc Visual Studio, necessitent des droits Administrateur lancer le serveur.

Relancez Visual Studio, et exécutez votre projet. Dans la systray (zone d'icône a coté de l'horloge) se trouve une icône "IIS Express", faire un clic-droit et sélectionner le menu "Afficher toutes les applications". Vous devez voir apparaître votre site deux fois ; une avec le localhost et l'autre avec votre adresse IP.

Pour revenir à l'état initial supprimer la règle IISExpressWeb au niveau du pare feux, et exécuter en tanq qu'administrateur :


netsh http delete urlacl "http://192.168.1.1:55713/"
 

Accès à IIS Local

Dans certains cas il peut être utile de simuler le fonctionnement sur le serveur de production sans passer par IISExpress, et sur un port spécifique. Pour cela vous devez modifier le fichier csproj en remplaçant ceci :

true 44300 http://localhost/:50339/

par cela :

false http://localhost/Patient

Netoyage des fichiers temporaires chrome

Après quelques mois de deboguage intenssif certains dossiers temporaire de chrome peuvent grossir enormément. Dans ce cas vous pourrez faire le ménage dans le dossier suivant : et voir le téléchargement avec chrome://download-internals/


%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Default\
optimization_guide_prediction_model_downloads
 

réf: Accès à IIS Express depuis l’extérieur
http://blog.ygrenier.com/2014/06/acces-iis-express-external/

CHAPITRE 2 - Interfaces notions avancées.

2.1. Extensibilité avec les interfaces

Il existe différentes sources de données SGBDR, CSV, Web Services, Cloud. L'application n'a pas besoin de savoir comment accéder aux données ou comment faire des appels de webservice. Alors on crée une couche entre l'application et les données. Le Repository pattern: couche utilisant les interfaces/ collection pour accéder aux objets du domaine. Il s'agit de notre référentiel. Nous pouvons alors créer des implémentations différentes du référentiel qui adhèrent au contrat. pour créer des référentiels supplémentairess, il suffit d'adhérer à l'interface et tout fonctionne automatiquement. Ceux-ci sont souvent désignés comme les opérations CRUD (Créate, Read, Update, Delete).

2.2. Implementation d'interface explicite


public class Catalog : ISaveable {           Catalog catalog = new Catalog();
  public string Save() {}                    catalog.Save();
  // explicite                               ISaveable saveable = new Catalog();
  string ISaveable.Save() {}                 saveable.Save();
}                                            ((ISaveable)catalog).Save();
 

En C# il est possible de surcharger les fonctions avec différents paramètres, mais nous n'avons pas le droit à des méthodes de surcharge en fonction du type de retour seulement.

2.3. RepositoryFactory


namespace PersonRepository.Interface {
public interface IPersonRepository
{
  IEnumerable<Person> GetPeople();

 

Dossier References Add Service Reference, Discover, sélectionner PersonService.svc Nommer le service, aller dans Advanced Modifier le Collection type: System.Collections.Generic.List Renommer Class1.cs en ServiceRepository.cs et modifier la classe pour implémenter l'interface IPersonRepository


using PersonRepository.Service.MyService;
namespace PersonRepository.Service {
  public class ServiceRepository : IPersonRepository
  {
        PersonServiceClient serviceProxy = new PersonServiceClient();
    public IEnumerable<Person> GetPeople()
    {
      return serviceProxy.GetPeople();
    }
  }
 

 

Dossier Repositories Add Existing Project


public class CSVRepository : IPersonRepository
{
  public CSVRepository() {
    var filename = ConfigurationManager.AppSettings["CSVFileName"];
    ...
  }
  public IEnumerable<Person> GetPeople() {}
 


namespace PeopleViewer {
public static class RepositoryFactory {
  public static IPersonRepository GetRepository(string repoT)
  {
    switch(repoT)
    {
      case "Service": repo = new ServiceRepository();
      case "CSV": repo = new CSVRepository();
    }
    return repo;
  }

private void FetchData (string repoT)
{
  ClearLitBox();
  IPersonRepository repository = RepositoryFactory.GetRepository(repoT);
  var people = repository.GetPeople();
  foreach(var person in people)
    PersonListBox.Items.Add(person);
}
FetchData("CSV");
 

2.4. La Couche View Model MVVM

Application ViewModel Repository DataStorage

Le but du test unitaire est de tester de petites parties de code pour être sûr qu'une foie assemblés les différentes parties de code fonctionnent bien ensemble. Les parties de code qui interagissent avec l'interface utilisateur compliquent la réalisation des tests unitaire puiqu'il faut créer des object fenêttre, arguments, etc. qui ne sont pas forcement utilisés. Il vau mieux réaliser une séparation entre le code et l'interface graphique (viewModel)

1) Isoler l'interface utilisateur en utilisant le dataBinding 2) Créer des FakeRepository qu'on peut utiliser pour les tests.


public class MainViewModel : INotifyPropertyChanged
{
  private IPersonRepository repository;
  private IEnumerable<Person> people;
  public MainViewModel() {
    repository = RepositoryFactory.GetRepository();
  }
  public void FetchData() {
    People = repository.GetPeople();
  }
}

<ListBox ItemsSource="{Binding People}"
MainViewModel viewModel;
public MainWindow()
{
  InitializeComponent();
  viewModel = new MainViewModel();
  this.DataContext = viewModel;
}
private void FechButton_Click(object sender, RoutedEventArgs e)
{
  viewModel.FetchData();
  ShowRepositoryType();
}
 

2.5. Chargement dynamique


App.config:
<!-- Settings for Service Repository -->
<appSettings>
  <add key="RepositoryType" value="PersonRepository.Service.ServiceRepository,
    PersonRepository.Service, Version=1.0.0.0, Culture=neutral"
/>
</appSettings>

public static class RepositoryFactory
{
  public static IPersonRepository GetRepository()
  {
    string typeName = ConfigurationManager.AppSettings["RepositoryType"];
    Type repoType = Type.GetType(typeName);
    object repoInstance = Activator.CreateInstance(repoType);
    IPersonRepository repo = repoInstance as IPersonRepository;
    return repo;
  }
}
 

PeopleViewer.exe.config Il suffit de décommenter ceci pour changer de repository et de DLL


<appSettings>
  <add key="RepositoryType" value="PersonRepository.CSV.CSVRepository,
    PersonRepository.CSV, Version=1.0.0.0, Culture=neutral"
/>
</appSettings>
 

2.6. Dependency Injection

Il existe différents types d'injection: - Constructor injection, Property Injection, Method injection, Service Locator Il existe plusieur containeur de dependency injection: - Unity, StructureMap, Autofac, Ninject, Castle Windsor, etc.

Utilisation de Service Locator: notre classe demande un service, notre SimpleContainer, pour localiser une instance d'un type particulier que cette classe a besoin.


namespace PeopleViewer
{
  public class MainViewModel : INotifyPropertyChanged
  {
    private IPersonRepository repository;
    private IEnumerable<Person> people;
    public MainViewModel() {
      repository = SimpleContainer.Get<IPersonRepository>();
    }
    public void FetchData() {
      People = repository.GetPeople();
    }
  }
}
namespace PeopleViewer
{
  public static class SimpleContainer
  {
    public static void MapInstance<T>(object concreteType)
    {
      Type instanceType = typeof(T);
      if (InstanceCatalog.ContainsKey(instanceType))
        InstanceCatalog.Remove(instanceType);
      InstanceCatalog.Add(instanceType, concreteType);
    }
    public static T Get<T>() where T : class
    {
      T instance = GetFromInstanceCatalog<T>();
      if (instance == null)
        instance = GetFromConfig<T>();
      return instance;
    }
    private static T GetFromConfig<T>() where T : class    
    {
      string typeName=ConfigurationManager.AppSettings[typeOf(T).ToString()];
      if (string.IsNullOrEmpty(typeName)) return null;
      Type repoType = Type.GetType(typeName);
      object repoInstance = Activator.CreateInstance(repoType);
      T repo = repoInstance as T;
      return repo;
    }
  }
}
 

App.config:


<!-- Settings for Service Repository -->
<appSettings>
  <!-- <add key="RepositoryType" -->
  <add key="PersonRepository.Interface.IPersonRepository"
    value="PersonRepository.Service.ServiceRepository,
    PersonRepository.Service, Version=1.0.0.0, Culture=neutral"
/>
</appSettings>
 

Ainsi le programme recherche le type dans le fichier de configuration, et charge cette assemblée en utilisant la réflexion, et crée effectivement cette instance. La différence est notre clé. Au lieu d'avoir notre PersonRepository clé, notre clé est le typeName complet. Notre conteneur peut analyser cette clé à partir de ce type générique que nous lui passons.


namespace PeopleViewer
{
  public partial class App : Application
  {
    protected override void OnStartup(StartupEventArgs e)
    {
      base.OnStartup(e);
      FakeRepository repository = new FakeRepository();
      SimpleContainer.MapInstance<IPersonRepository>(repository);
    }
  }
}
 

2.7. Mocking

Mocking permet de créer des objets de l'espace réservé. Avec ces objets, pas besoin de mettre en oeuvre toute la classe, il suffit de fournir un comportement pour les méthodes que nous prévoyons d'utiliser.

  • Créer Placeholder object dans la mémoire
  • implémenter uniquement les comportements qui nous interresse.

Mocking Frameworks

  • RhinoMocks, Microsoft Fakes, Moq,

namespace PeopleViewer.Test
{
  [TestClass]
  public class FakeVMTest
  {
    [TestInitialize]
    public void Setup()
    {
      FakeRepository repository = new FakeRepository();
      SimpleContainer.MapInstance<IPersonRepository>(repository);
    }
    [TestMethod]
    public void People_WithFakeOnFetchData_IsPopulated()
    {
      var viewModel = new MainViewModel();
      viewModel.FetchData();
      Assert.IsNotNull(viewModel.People);
    }
  }
}
->
namespace PeopleViewer.Test
{
  [TestClass]
  public class MockVMTest
  {
    [TestInitialize]
    public void Setup()
    {
      var people = new List<Person>() {
        new Person() {FirstName="John",... },...
      };
      var mockRepository = new Mock<IPersonRepository>();
      mockRepository.Setup(r => r.GetPeople()).Returns(people);

      SimpleContainer.MapInstance<IPersonRepository>(mockRepository.Object);
    }
    [TestMethod]
    public void People_WithMokOnFetchData_IsPopulated()
    {
      var viewModel = new MainViewModel();
      viewModel.FetchData();
      Assert.IsNotNull(viewModel.People);
    }
  }
}
 

2.8. Méthodes d'extension

Les méthodes d'extension permettent d'utiliser sa propre logique, dans une base de code externe. Visual Studio marque ces méthodes non native d'une flèche. ReSharper permet d'importer les namespaces. Utilisation de ildasm et dotPeek de JetBrains.

Déclaration d'une méthode d'extension. Le mot clé this permet d'étendre ce type.


  public static string ToLegacyFormat(this DateTime dateTime)

  var date = new DateTime(1920, 12, 31);
  date.ToLegacyFormat();
 

Pour pouvoir utiliser notre méthode d'extension il faut ajouter using namespace; Une méthode d'extension peut être créée dans un espace de nom natif. Pour cela il faut créer un répertoire, et une classe que l'on rennome en l'espace de nom.


  namespace System
  {
    public static class DateTimeExtensions
    {
      public statis string ToXmlDateTime(this DateTime dateTime)
  var dateTime = new DateTime(2013, 10, 24, 13, 10, 15, 951);
  string xmlDateTime = DateTimeExtensions.ToXmlDateTime(dateTime);
 

Il est également possible d'étendre les interfaces et les collections:


  public static IEnumerable<ReferenceDataItem>
    GetItemsByCode(this IReferenceDataSource source, string code)

  public static IEnumerable<ReferenceDataItem>
    GetAllItemsByCode(this IReferenceDataSource[] source, string code)
  ou
  public static IEnumerable<ReferenceDataItem>
    GetAllItemsByCode(this IEnumerable source, string code)
  {
    var items = new List<ReferenceDataItem>
    foreach(var source in sources)
    {
      var refDataSource = source as IReferenceDataSource;
      if (refDataSource != null)
        items.AddRange(refDataSource.GetItemsByCode(code));
    }
  }
  ou en utilisant linq
  public static IEnumerable<ReferenceDataItem>
    GetAllItemsByCode(this IEnumerable<IReferenceDataSource> source, string code)
  {
    return sources.SelectMany(x => x.GetItemsByCode(code));
  }
 

Il est également possible d'étendre n'importe quoi de n'importe quel type.


  public class ObjectExtensions
  {
    public static string ToJsonString(this object obj)
    {
      return JsonConvert.SerializeObject(obj);
    }
    public static string GetJsonTypeDescription(this object obj)
    {
      var description = obj.GetType().GetDescription();
      return description.ToJsonString();
    }
  }

  public class TypeExtensions
  {
    public static TypeDescription GetDescription(this Type type)
    {
      return new TypeDescription
      {
        AssemblyQualifiedName = type.AssemblyQualifiedName,
        FullName = type.FullName
      };
    }
  }

  public class TypeDescription
  {
    public string FullName { get; set; }
    public string AssemblyQualifiedName { get; set; }
  }
 

Advanced Extension Methods

Contournement pour les classes scéllés, méthode dangereuse si modification le la classe native. Problèmes de garbage collector, pas toujours implémentable sur les classe qui n'implémentent pas IDisposable.

Cette derniere methode rencontre un problème de vitesse à cause du temps d'accés avec le nom, ou si le déveoppeur change le nom de la propriété ex class Instrumentation private var _startedAt -> startedAt Mais il est possible d'utiliser un id


namespace Sixeyed.Extensions.Advanced.Demo1
{
  public sealed class Instrumentation
  {
    public Guid Id { get; private set; }
    private DateTime _startedAt;
    public string ProcessName { get; set; }

    public Instrumentation() {
      Id = Guid.NewGuid();
    }
   
    public void Start() {
      _startedAt = DateTime.Now;
    }
   
    public int GetElapsedTime() {
      return (int) Math.Round(
        new TimeSpan(
          DateTime.Now.Ticks - _startedAt.Ticks
        ).TotalSeconds, 0);
    }
  }
}

namespace Sixeyed.Extensions.Advanced.Demo1
{
  public static class InstrumentationExtensions
  {
    private static Dictionary<Guid, Stopwatch> _Stopwatches
     = new Dictionary<Guid, Stopwatch>();
    public static double GetPreciseElapsedTime(this Instrumentation instrumentation)
    {
      var fieldInfo = typeof(Instrumentation).GetField("_startedAt", BindingFlags.Instance | BindingFlags.NonPublic);
      var startedAt = (DateTime)fieldInfo.GetValue(instrumentation);
      return new TimeSpan(DateTime.Now.Ticks - startedAt.Ticks).TotalSeconds;
    }

    public static void StartWithPrecision(this Instrumentation instrumentation)
    {
      _Stopwatches[instrumentation.Id] = Stopwatch.StartNew();
    }
   
    public static long GetReallyPreciseElapsedTime(this Instrumentation instrumentation)
    {
      return _Stopwatches[instrumentation.Id].ElapsedMilliseconds;
    }
  }
}
 

On ne peut pas étendre les types imbriqués (ex sous classe privé) AssemblyInfo.cs: permet d'acceder aux sous classes internal [assembly: InternalsVisibleTo("namespace")]


namespace Sixeyed.Extensions.Advanced.Demo2
{
  internal abstract class Class0
  {
    public virtual string GetString0() { return "abc"; }
    protected virtual string GetString00() { return "abcd"; }
  }

  internal class Class1 : Class0
  {
    public string GetString1() { return "a"; }

    internal class Class2 : Class0
    {
      public override string GetString0() { return "xyz"; }
      internal string GetString2() { return "b"; }

      private class Class3
      {
        private string GetString3() { return "c"; }
      }
    }
  }
}

namespace Sixeyed.Extensions.Advanced.Demo2
{
  internal static class InternalClassExtensions
  {
    public static string GetString0Upper(this Class0 obj)
    {
      return obj.GetString0().ToUpper();
    }

    /*
    public static string GetString00Upper(this Class0 obj)
    {
      return obj.GetString00().ToUpper();
    }
     * */

   
    public static string GetString1Upper(this Class1 obj)
    {
      return obj.GetString1().ToUpper();
    }
   
    public static string GetString2Upper(this Class1.Class2 obj)
    {
      return obj.GetString2().ToUpper();
    }
   
    public static string GetString3Upper(this object obj)
    {
      var upper = string.Empty;
      var type3 = typeof(Class1.Class2).GetNestedType("Class3", BindingFlags.NonPublic);
      if (obj.GetType() == type3)
      {
        var method = type3.GetMethod("GetString3", BindingFlags.NonPublic | BindingFlags.Instance);
        var string3 = method.Invoke(obj, null) as string;
        upper = string3.ToUpper();
      }
      return upper;
    }  
  }
}

[TestMethod]
public void Class3()
{
  var type3 = typeof(Class1.Class2).GetNestedType("Class3", BindingFlags.NonPublic);
  var methodInfo = type3.GetMethod("GetString3", BindingFlags.NonPublic | BindingFlags.Instance);
  var obj3 = Activator.CreateInstance(type3);
  Assert.AreEqual("c", methodInfo.Invoke(obj3, null));
  Assert.AreEqual("C", obj3.GetString3Upper());
}
 

Il est possible d'utiliser les méthodes d'extension dans un autre environnemant en créant un "Portable Class Library". Créer une classe d'extension comme avant.


ex:
namespace System {
  public static class StringExtensions {
    public static string FormatWith(this string format, params object[] args) {
      return void string.Format(format, args);
    }

 

Si on change la cible en ".NET Framework 3.5 Client Profile" par ex. , il n'est plus possible d'ajouter la référence de la classe de méthode étendue portable, car le projet en cours ne supporte pas les références aux librairies portable. Pour palier à ce problème il suffit d'ajouter un lien: Créer un nouveau projet et ajouter la classe de la méthode d'extension en sélectionnant "Add as Link".

Sur les methodes d'extensions de comparaison, et est vaut mieux utiliser un type IComparable pour avoir une erreur du compilateur.

Extension Method Library

  • Core, Ref lection, Entity Framework
  • WCF, WebAPI, ASP.NET MVC

2.9. Signal-R

Installation des bibliothèques et des scripts clients :
https://docs.microsoft.com/en-us/aspnet/signalr/overview/getting-started/tutorial-getting-started-with-signalr-and-mvc

SignalR Client
https://docs.microsoft.com/en-us/aspnet/core/signalr/javascript-client?view=aspnetcore-2.1

CHAPITRE 3 - LINQ Fundamentals

3.1. Introduction

La plupart des requêtes dans la documentation LINQ (Language Integrated Query) d’introduction sont écrites à l’aide de la syntaxe de requête déclarative LINQ. Toutefois, la syntaxe de requête doit être traduite en appels de méthode pour le Common Langage Runtime (CLR) . NET lorsque le code est compilé. Ces appels de méthode appellent les opérateurs de requête standard, qui ont des noms tels que Where, Select, GroupBy, Join, Max et Average. Vous pouvez les appeler directement en utilisant la syntaxe de méthode à la place de la syntaxe de requête.

3.2. Comparaison avec les tableau Array

Tableau d'objets: object[] objArray = new object[3]; Le type Array et covariant, ex: object[] objArr2 = daysOfWeek; objArr2[0] = 3; Array.CopyTo .Reverse, .Sort IComparer, .IndexOf, .LastIndexOf Array.FindIndex(daysOfWeek, x => x[0] == 'W'); Array.FindAll(daysOfWeek, x => x.Length == 6); Array.BinarySearch(sortedDays, "Sunday"); Recherche dichotomique LINQ possède les méthodes de Array pour toutes les collections. LINQ retourne de nouveaux tableaux alors que Arrays modifie directement le tableau. LINQ est moins performant que les tableaux. La covariance Array peut caster un tableau de différent type, en tableau de type de base. Ceci peut transformer des erreurs de compilation en erreurs d'exécution Ce qui n'est pas le cas de IEnumerable ou IEnumerator. IEnumerable ajoute la capacité de réaliser un foreach. Il possède une architec -ture qui permet plusieurs clients. Il intègre une protection, en cas de modifications de la collection en cours de lecture, avec un numéro de version.

ICollection IReadOnlyCollection ce qui est dans la collection: IEnumerable + Count + Contains() copie dans un tableau CopyTo() Modifier la collection: Add(), Remove(), Clear(), IsReadOnly ICollection collection = (ICollection)dayOfWeek;

IList IReadOnlyList ce qui est dans la collection: this[index] IndexOf() copie dans un tableau CopyTo() Modifier la collection: Insert(), RemoveAt()

IDictionary, ajoute à ICollection la capacité à rechercher IReadOnlyDictionary semblable à IList mais avec une clé au lieu d'un index ce qui est dans la collection: this[key] Keys, Values, TryGetValue() ConstrainsKey Modifier la collection: Add(), Remove()

ISet operations: ExceptWith(), IntersectWith(), SymmetricExceptWith(), UnionWith() comparaisons: IsProperSubsetOf(), IsProperSupersetOf(), IsSubsetOf, Overlaps, SetEquals Modifier la collection: Add()

Une classe avec des propriétés publiques, est considérée comme un objet: ex: var custs = new List { new Customer { City = "Phoenix", FistName = "John", ID=125 } new Customer { City = "Phoenix", FistName = "Jane", ID=215 } }; using System.Linq; var phxCusts = custs .Where(c => c.City == "Phoenix" && c.ID < 500) .OrderBy(c => c.FistName); foreach (var cust in phxCusts)

filtrer un dataSouce avec linq IEnumerable leader = from l in result.CatalogResponse where (l.ManufacturerCode == "BBG") select l; //where (l.ManufacturerCode.Contains("BBG")) select l; dataGridView1.DataSource = leader.ToArray();

transformer un dataSouce avec linq var leader = from l in result.CatalogResponse where (l.ManufacturerCode == "BBG") select new { l.CountryCode, l.Date, l.Description, l.DistributorCode, l.ManufacturerCode }; dataGridView1.DataSource = leader.ToArray();

3.3. GroupJoin


IList<Student> studentList = new List<Student>() {
  new Student() { StudentID = 1, StudentName = "John", StandardID =1 },
  new Student() { StudentID = 2, StudentName = "Moin", StandardID =1 },
  new Student() { StudentID = 3, StudentName = "Bill", StandardID =2 },
  new Student() { StudentID = 4, StudentName = "Ram",  StandardID =2 },
  new Student() { StudentID = 5, StudentName = "Ron" } };

IList<Standard> standardList = new List<Standard>() {
  new Standard(){ StandardID = 1, StandardName="Standard 1"},
  new Standard(){ StandardID = 2, StandardName="Standard 2"},
  new Standard(){ StandardID = 3, StandardName="Standard 3"} };

var groupJoin = standardList.GroupJoin(
  studentList,            // inner sequence
  std => std.StandardID,  // outerKeySelector
  s => s.StandardID,      // innerKeySelector
  (std, studentsGroup) => // resultSelector
  new {
      Students = studentsGroup,                        
      StandarFulldName = std.StandardName                   Output:      
  } // { Students:[...], StandarFulldName:'Standard 1' }    Standard 1:  
);                                                          John,        
                                                            Moin,        
foreach (var item in groupJoin)  {                          Standard 2:  
  Console.WriteLine(item.StandarFulldName );                Bill,        
  foreach(var stud in item.Students)                        Ram,        
    Console.WriteLine(stud.StudentName); }                  Standard 3:  
 

Dans le cas d'une jointure sur une clé étrangère pour un relation 1-1, voici comment filtrer l'ensemble des résultats sur un critaire de la table étrangère.


.GroupJoin( ...
.Where(x => (nameTofind == null
 || x.Students.DefaultIfEmpty().First().StudentName.ToUpper().Contains(
  nameTofind.ToUpper())))

Cependant avec entity framework il est conseillé de se servir d'une propriété de
navigation dans le cas d'
une relation 1-1

Voici un example complexe:

  var nameContains = dbContext.Patients
  .Where(p => p.NormalisedText.Contains(patientSearch.SearchKey.ToString()))
  .OrderBy(p => p.NormalisedText)
  .GroupJoin(dbContext.PatientAddresses,
      pat => pat.Id,
      adr => adr.PatientId,
      (pat, adr) => new { pat, adr })
  .SelectMany(result => result.adr.DefaultIfEmpty(),
      (pat, adr) => new { pat.pat, adr })
  .GroupJoin(dbContext.PatientContacts,
      pat => pat.pat.Id,
      ctx => ctx.PatientId,
      (pat, ctx) => new { pat.pat, pat.adr, ctx })
  .SelectMany(result => result.ctx.DefaultIfEmpty(),
      (pat, ctx) => new { pat.pat, pat.adr, ctx })
  .Where(h => h.ctx == null || validComModes.Contains(h.ctx.ComModeId))
  .Select(result => new PatientSearchProjection
  {
     DatabaseId = result.pat.Id,
     Name = result.pat.FullName,
     PatientAddress = result.adr,
     DateOfBirth = result.pat.DateOfBirth,
     DisplayId = result.pat.DisplayIdentifier,
     PreferredPhoneNb = result.ctx != null ? result.ctx.ContactReference : null,
     PriorityOrder = result.ctx != null ? result.ctx.PriorityOrder : 0,
     ComModeId = result.ctx != null ? result.ctx.ComModeId : default(Guid)
  })
  .OrderBy(d => d.DatabaseId)
  .ThenBy(d => d.PriorityOrder);

Voici un example optimisé en vitesse avec un left outer join:

var listPrats = pracChecked.Split(';').Select(c => int.Parse(c)).ToArray();
var listStatus = statusChecked.Split(';').Select(c => int.Parse(c)).ToArray();
var listStucts = structureCheked.Split(';').Select(c => int.Parse(c)).ToArray();

_result = ctxStatistiques.User
  .Where(u => u.insertDateUser >= StartDate
      && u.insertDateUser < EndDate
      && listStatus.Contains(u.statusUser)
      && u.isDeletedUser == false)
  .GroupJoin(ctxStatistiques.Patient,
      usr => usr.idUser,
      pat => pat.idUser,
      (usr, pat) => new { usr, pat })
  .SelectMany(result1 => result1.pat,
  (usr, pat) => new { usr, pat })
  .GroupJoin(ctxStatistiques.MailingAddress,
      usrpat => usrpat.usr.usr.idUser,
      ma => ma.idUser,
      (usr, ma) => new { usr, ma })
  .SelectMany(result => result.ma.DefaultIfEmpty(),
  (result, ma) => new CoordonneesDesPatients
  {
      idCreatedStructure = result.usr.pat.idCreatedStructure,
      idDoctor = result.usr.pat.idDoctor,
      lastNameUser = result.usr.usr.usr.lastNameUser,
      firstNameUser = result.usr.usr.usr.firstNameUser,
      emailPersonal = string.IsNullOrEmpty(result.usr.usr.usr.emailPersonal)
      ? result.usr.usr.usr.emailPersonal : String.Empty,
      statusUser = result.usr.usr.usr.statusUser
  })
  // patients dont le docteur en cours appartient a la structure en cours
  // patients créés dans la structure en cours s'il n'a pas encore de docteur
  .Where(
    j => ((j.idDoctor == null) && (listStucts.Contains(j.idCreatedStructure))
     || ((j.idDoctor != null) && listPrats.Contains((int)j.idDefaultDoctor)))
  )
  .OrderBy(d => d.lastNameUser)
  .ThenBy(d => d.firstNameUser)
  .ToList<CoordonneesDesPatients>();
 

réf: groupJoin
https://www.tutorialsteacher.com/linq/linq-joining-operator-groupjoin

Inner Join avec la syntaxe de requête


  reglements = (from e in db.EntryAccounting
    join pat in db.Patient on e.idPatient equals pat.idUser
    join ai in db.ActInvoice on e.ActInvoiceID equals ai.Id into x
    from invoice in x.DefaultIfEmpty()
    join copt in db.ChartOfPaymentTypeGlobal on e.ModeReglement.ModeReglementLibelle.IdChartOfPaymentTypeGlobal equals copt.IdChartOfPaymentTypeGlobal into pt
    from paymenttype in pt.DefaultIfEmpty()
    where (e.EntriesCluster1.TypeCluster == (int)ModeCluster.REGLEMENT_CREATION || e.EntriesCluster1.TypeCluster == (int)ModeCluster.REGLEMENT_ANNULATION)
      && e.EntriesCluster1.paClotureId == null
      && DbFunctions.TruncateTime(e.EntriesCluster1.Date) >= DbFunctions.TruncateTime(debutDatePrecloture)
      && DbFunctions.TruncateTime(e.EntriesCluster1.Date) <= DbFunctions.TruncateTime(DatePreCloture)
      && !e.EntriesCluster1.isDeleted
      && e.EntriesCluster1.MotifOD == null
      && e.idStructure == MyCurrentIdStructure
      && e.idPraticien == idPraticien
      && e.AccountNumber.StartsWith("411")
      && null != e.ModeReglement
      && e.isVirtualEntryAccounting == true
    select new ReglementAssures
    {
      Date = (null != e.EntriesCluster1.Date) ? e.EntriesCluster1.Date : invoice.Date,
      Facture = invoice != null ? invoice.Number : "",
      Matricule = pat.securitySocialNumberPatient,
      Nom = pat.User2.lastNameUser,
      Prenom = pat.User2.firstNameUser,
      Mode = (e.ModeReglement.ModeReglementLibelle != null) && (e.ModeReglement.ModeReglementLibelle.Type != null) ?
            ((CategoryModeReglementEnum)e.ModeReglement.ModeReglementLibelle.Type.indicatorNumberPaymentType).ToString()
            : (paymenttype.LabelPaymentType != null ? paymenttype.LabelPaymentType : CategoryModeReglementEnum.AUT.ToString()),
      Montant = e.Credit - e.Debit,
      Id = e.paEntryId,
      typeAction = e.EntriesCluster1.TypeCluster == (int)ModeCluster.REGLEMENT_CREATION ? "création règlement" : "annulation règlement"
    }).ToList();
 

3.4. Select

Si vous voulez toujours utiliser votre constructeur pour l'initialisation et non les propriétés (à des fins d'initialisation), énumérer la requête en appelant ToList() ou ToArray(), et ensuite utiliser Select(...). Ainsi, elle utilisera LINQ to Collections et la limitation de ne pas pouvoir appeler le constructeur avec des paramètres dans Select(...) disparaîtra. Erreur: "Only parameterless constructors and initializers are supported in LINQ to Entities"


var naleznosci = db.Naleznosci
      .Where(nalTmp => nalTmp.idDziecko == idDziec)
      .ToList() // Here comes transfer to LINQ to Collections.
      .Select(nalImp => new Payments
          (
              nalTmp.Dziecko.Imie,
              nalTmp.Dziecko.Nazwisko,
              nalTmp.Miesiace.Nazwa,
              nalTmp.Kwota,
              nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
              nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
              nalTmp.DataRozliczenia,
              nalTmp.TerminPlatnosci
          ))
      .ToList();
 

réf:
https://stackoverflow.com/questions/3571084/only-parameterless-constructors-and-initializers-are-supported-in-linq-to-entiti

3.5. OrderBy

Vous devrez effectuer le classement en mémoire puisqu'il n'existe aucun moyen de transmettre cette logique de classement à SQL. Vous pouvez le faire en utilisant AsEnumerable() pour changer le contexte d'une requête SQL à une requête en mémoire :


  var results = bookContext.Books
    .AsEnumerable()
    .OrderByDescending(bk => bk.Id)
    .ThenBy(bk => subGenereIds.IndexOf(bk.Id));
 

réf:
https://stackoverflow.com/questions/52107040/using-external-list-to-order-a-linq-query/52107381

3.6. GroupBy

persons.GroupBy(x => x.PersonId).Select(x => x) ou pour contrôler les doublons: persons.GroupBy(x => x.PersonId).Where(x => x.Count() > 1).Any(x => x)


  class Person  {                  class Result  {            
    public int PersonId;             public int PersonId;    
    public string car  ;             public List<string> Cars;
  }                                }                          

  public class Program
  {
    public static void Main()
    {
      List<Person> persons = new List<Person>()
      {
        new Person { PersonId = 1, car = "Ferrari" },
        new Person { PersonId = 1, car = "BMW" },
        new Person { PersonId = 2, car = "Audi"}
      };

      //With Query Syntax
   
      List<Result> results1 = (
        from p in persons
        group p by p.PersonId into g
        select new Result()
            {
                PersonId = g.Key,
                Cars = g.Select(c => c.car).ToList()
            }
        ).ToList();
   
      foreach (Result item in results1)
      {
        Console.WriteLine(item.PersonId);
        foreach(string car in item.Cars)  {
          Console.WriteLine(car);
        }
      }
   
      Console.WriteLine("-----------");
   
      //Method Syntax
   
      List<Result> results2 = persons
        .GroupBy(p => p.PersonId,                      // Resultat:
          (k, c) => new Result()                       // 1          
          {                                            // Ferrari    
            PersonId = k,                              // BMW        
            Cars = c.Select(cs => cs.car).ToList()     // 2          
          }                                            // Audi      
        ).ToList();                                    // -----------
                                                       // 1          
      foreach (Result item in results2)                // Ferrari    
      {                                                // BMW        
        Console.WriteLine(item.PersonId);              // 2          
        foreach(string car in item.Cars)  {            // Audi      
          Console.WriteLine(car);
        }
      }
    }
  }
 

Exemple pratique d'implementation GroupBy :


  var dbJournalRecettesTmp = Ctx.JournalRecetteView.Where(...
  List<JournalRecetteView> dbJournalRecettes = new List<JournalRecetteView>();
  dbJournalRecettesTmp.ForEach(j => {
    if (lstTypeOperationCode.Contains("CHQ") && (j.PaymentType == 1
     || j.OperationCode == "CHQ" || j.OperationCode == "CHQTP"))
    {
        dbJournalRecettes.Add(j);
    }
  ...
  var lstRN = dbJournalRecettes.Where(x => x.idOrgPaymentCluster != null)
  .GroupBy(x => x.idOrganizationPaymentCluster).ToList();

  if (lstRN.Count() > 0)
  {

    foreach (var item in lstRN)
    {
      var orgPaymentCluster = item.First().orgPaymentCluster;
      Recette recette = new Recette();
      recette.JRId = item.First().idOrgnPaymentCluster.Value;
      recette.AmountTotal = item.Sum(x => x.Montant).Value;
      recette.StrOperateur = string.Join(",",
        item.Select(x => x.Praticien).Distinct());
    }
 

Pour concatener un champs lors d'un groupBy en Linq :


  _context.Log.GroupBy(l => new { l.UserId, l.dates.Date, l.Deptname })
  .Select(g => new { g.Key.UserId, g.Key.Date, g.Key.Deptname,
    Log = string.Join(",", g.Select(i => i.times)) });
 

réf:
https://stackoverflow.com/questions/7325278/group-by-in-linq/7325306

Regroupement de plusieurs colonnes


.GroupBy(res => new { res.column1, res.column2, res.column3 },
    (k, c) => new
    {
        minId = c.Min(e => e.joinedtable.Id),
        AssociatedIds = c.Select(e => joinedtable.Id),
        column1 = k.column1,
        column2 = k.column2
    }
);
 

3.7. Having


  var data = _context.Schedule1.Where(e.status == 0)
  .GroupBy(e => e.idSchedulePayment, (k, c) => new {
      c.FirstOrDefault().idStructure,
      c.FirstOrDefault().idPatient,
      nomPrenomPatient = c.FirstOrDefault().Patient.User2.lastNameUser + " "
        + c.FirstOrDefault().Patient.User2.firstNameUser,
      amount = c.Sum(x => x.amount),
      NbSchedules = c.Count(),
      ongoingamount = c.Where(x => x.treatmentDate == null).Any() ?
        c.Where(x => x.treatmentDate == null).Sum(x => x.amount) : 0,
      isSchedulePaymentPassed = c.Where(x => x.treatmentDate == null
        && x.BeginDate < DateTime.Now).Any(),
      c.FirstOrDefault().Label,
      c.FirstOrDefault().creationDate,
      lines = c.OrderBy(x => x.BeginDate)
  })
  .Where(grp => grp.ongoingamount > 0)
  .OrderBy(e => e.creationDate)
  .ToList();
 

réf: Linq with group by having count
https://stackoverflow.com/questions/2078736/linq-with-group-by-having-count

3.8. Linq-to-XML


<Appointments>
  <Appointment>
    <orderId>0</orderId>
    <link>774503dd-3349-47c7-8d3a-d82cb75dad3c</link>
  </Appointment>
  <Appointment>
    <orderId>1</orderId>
    <link>2ab9b3e2-621b-48a5-8201-0145478e19ec</link>
  </Appointment>
</Appointments>
 

  public class AppointmentXml
  {
      public string orderId;
      public string link;
  }

  // https://stackoverflow.com/questions/6228315/convert-xml-to-datatable
  public static List<AppointmentXml> readAppointmentXml(string xml)
  {
      TextReader tr = new StringReader(xml);
      XDocument doc = XDocument.Load(tr);
      var rows = doc.Descendants("Appointment").Select(el => new AppointmentXml
      {
          orderId = el.Element("orderId").Value,
          link = el.Element("link").Value
      }).ToList();
      return rows;
  }
 

réf: https://stackoverflow.com/questions/6228315/convert-xml-to-datatable

https://stackoverflow.com/questions/6228315/convert-xml-to-datatable

CHAPITRE 4 - .Net

4.1. Guide de démarrage

Créer une nouvelle "Application web (.Net Framework)", avec le modèle MVC. Aller dans "Extensions", "Gérer les extensions". Sur la gauche cliquer, "En ligne", et rechercher "productivity power tools". Un autre pluggin est "web essentials". En payant il y ReSharper aussi.

Visual Studio Online, est gratuit pour un maximum de 5 utilisateurs. Lancé en 2013, VS Online est un ensemble de services cloud pour les utilisateurs de VS qui était auparavant connu sous le nom de Team Foundation Studio TFS. VS Online permet aux équipes de dévs de coopérer sur des dépôts de code source privés stockés dans le cloud. Il prend en charge des outils de gestion de projet tels que les tableaux Kanban, et peut être utilisé avec VS, Eclipse, Xcode et d'autres clients Git. Au-delà des 5 utilisateurs gratuits, le prix de base est de 20 USD par utilisateur et par mois https://app.vssps.visualstudio.com/

Le demarrage d'un projet necessite, une étude préalable du cahier des charge et d'en déduire les différents cas d'usages (use cases), et leur dépendances. Par exemple un site pour suivre les artistes, avec une gestion de leur concerts. On souligne les cas d'usages principaux tel que: "Ajout de concert", les usages secondaires tel que suppression de concert, sont dans le meme domaine d'applicat ion que l'ajout. on définit un ordre d'implémentation, de prèférence permettant de montrer la fonctionnalité principale du début à la fin, cela permet de créer le skelette de l'application, et d'y rajoutter de nouvelles fonctionnalités.

Dans VS Online on vat créer un projet; saisir le nom un projet, puis dans avancé sélectionner le process Agile. Allez dans Backlogs et faire "+ New Work Item", pour ajoutter les user stories. Puis placez les stories dans les itérations selon l'ordre choisit.

4.2. Création du domain model en code First

Aller dans Outils, Gestionnaire de package NuGet. Selectionner le projet, et dans parcourir rechercher entityframework, et enfin cliquer sur Installer. Aller dans Outils, Gestionnaire de package NuGet, Console du Gestionnaire de package et taper enable-migrations. Si une erreur survient, installer la version 6.1.3 . Créer sous le dossier Models, une classe "MyDbContext" qui hérite de DbContext Puis relancer enable-migrations. VS vat créer un fichier "Configuration.cs" dans le dossier Migrations.

Installer le package NuGet "Microsoft.AspNet.Identity.EntityFramework.2.2.3" Installer le package NuGet "Microsoft.Owin"

CHAPITRE 4 - .Net

CHAPITRE 4 - .Net Core

4.1. C'est quoi Asp.Net Core

Asp.Net Core est la réponse à Node.js qui consiste à faire tourner du javascript coté serveur, qui a défrayé la chronique en montrant qu'en fait c'était le serveur le plus rapide de la planète, celui qui montait le plus en charge, et qui traitait le mieux les requêttes. En Node.js on a un ensemble de package pour référence et puis on avance avec npm

Asp.Net Core est open source multi plateformes, tout comme le feu projet rotor. c'est un re design de ASP dotnet, les équipes de production sont repartis pratiquement de zéro, pour s'enlever de quelque chose qui a 15 ans d'existance, et de pouvoir s'adapter du web d'aujourd'hui, c'est-à-dire léger flexible et qui est capable d'utiliser tous les framworks de dernière génération javascript. Il faut savoir que Asp.Net était basé sur "system.net.dll", qui est une dll qui fesait uniquement partie de windows. Aujourd'hui asp.net core s'est enlevée de ce système là, pour être complètement agnostique à la plateforme et fonctionne avec des packages nugets (qui est l'équivalent de npm).

Il faut savoir que la carte asp.net était devenue un poil trop lourde avec le temps. Initialement l'idée c'était qu'avec asp net web forms, de dire à des développeurs desktop du début des années 2000 : "ben voilà en utilisant les mêmes techno vous pouvez faire du web", et donc du coup ça veut dire que ça s'appuyait sur plein de librairies windows très grosse, il y avait toute la partie systeme.foms, systeme.xml, et ça avec le temps c'est devenu un handicap. Donc je pense que c'était vraiment absolument vital de repartir sur un truc très léger adapté au web d'aujourd'hui de manière à pouvoir enlever tous le legacy qui nous embêtent aujourd'hui, et avec lesquels on ne pourra pas avancer parce que il ya trop de choses qui sont basés dessus.

On ne croit plus à cette histoire de dire: "on a des enormes composants qu'on va coder comme des objets desktop et puis on ne fait pas attention qu'il y a le web entre les deux. Ces trucs là aujourd'hui les gens n'en veulent plus. Ils veulent quelque chose léger, et des choses en ligne de commande parce que c'est comme ça que ça fonctionne, parce que l'on a besoin de la ligne de commande, avec le coté devOps dans lequel on veut pouvoir automatiser les choses. Et puis on veut pouvoir déployer n'importe où, n'importe comment. Aujourd'hui avec la ligne de commande, on sépare complètement la partie serveur avec la partie front. Avec Asp.net web forms et même avec MVC, on tirait les framework javascript sous forme de paquets nugets, ce qui est un non sens. Pour faire du développement front il vaut mieux utiliser des outils front que de ramener des librairies. Par exemple: React, Angular, Vue, Knockout (issue du monde Microsoft)

ASP.NET Core prend en charge l’interface Open Web pour . NET (OWIN). OWIN permet que les applications web soient dissociées des serveurs web. Elle définit une façon standard d’utiliser un intergiciel (middleware) dans un pipeline pour gérer les requêtes et les réponses associées. Les applications ASP. NET Core et l’intergiciel peuvent interagir avec les applications OWIN, les serveurs et l’intergiciel. OWIN fournit une couche de dissociation qui permet à deux frameworks ayant des modèles d’objets distincts d’être utilisés ensemble. C'est comme ça qu'on peut avoir différents types de serveurs aujourd'hui; on a IIS, on a le http listener, on a Kestrel, et d'autres serveurs open source. et tous ces trucs là sont capables de communiquer avec x implémentation web donc à savoir; Asp net, mvc core bien sûr, mais aussi des framework open source comme Nancy ou d'autres choses derrière.

.Net Core contient:

  • ASP.Net MVC
  • WebAPI
  • Dependency Injection (Autofac)
  • Model binding
  • Entity Framework
  • Tag Helpers
  • Bundler Minifier
  • Razor
  • Logger
  • SignalR

VsCode est un fork de l'éditeur Atom de github. C'est un projet open source qui s'appelle OmniSharp qui apporte toute l'intellicence et toute l'intelligence à VSCose. Il fait parti maintenant de .Net fundation. C'est un petit serveur qui

va tourner en tâche de fond sur votre machine, et qui va analyser le répertoire de code de votre projet et qui va fournir toute l'analyse du code. L'intérêt de cette mécanique c'est que que l'on peut utiliser n'importe quel éditeur. Il suffit d'avoir un petit plugin dans l'éditeur qui est capable d'aller causer avec le petit serveur web. Donc maintenant on peut utiliser VSCode, Vim, Emacs, et avoir de l'intelicence, de la compilation d'un projet .Net Core.


  public class Program
  {
    public static void Main(string[] args)
    {
      var host = new WebHostBuilder()
          .UseKestrel()
          .UseStartup<Startup>()
          .Build();
      host.Run();
    }
  }

  public class Startup
  {
    public void Configure(IApplicationBuilder app)
    {
      //app.UseWelcomePage();
      app.Run(async (context) => {
        awaitcontext.Response.WriteAsync(
          "Hello World!");
      });
    }
  }
 

On pourrait s'arrêter là, et dire on code tout notre application avec ça, aucun problème: tiens on demande la page d'accueil, je retourne le html avec un body.. Mais l'inconvénient c'est que ça va être inbitable. Le middleware MVC permet de faire ça. Tout ce que j'ai à faire c'est dans mon startup dire: je vais faire du mvc avec la commande app.useMvc().

Il s'agit d'une méthode d'extention qui est réellement codée dans le package MVC qui configure un middleware et son travail c'est de dire: puisqu'on va voir plein de requêtes entrantes on va essayer de découper un peu les choses et on va utiliser un modèle un pattern puisqu'on utilise souvent des patterns pour simplifier les grosses applications , et ce pattern c'est model view controller. MVC apporte également la testabilité, la répartition des rôles et une structure cliare et connue. L'inconvéniant est que ça rajoute de la complexité.

URL: Détermination de la route Action d'un (Méthode) Récupère les données à passer à la vue controleur: (classe) Si POST, traite les données remontantes Vue ou Json: Produit le HTML (Razor) ou du Json si c'est une webAPI

4.2. Asp.Net Core au plus simple

Dans Visual Studio faire New Project, "ASP.NET Core Web Application (.NETCore)". Puis Web Application car c'est le minimum pour générer des pages html. ou taper sur la console dotnet new mvc dans un dossier WebApplicationMUG. Bien qu'il soit encore possible d'utiliser l'ancien middleware UseMvc() ou UseRouter() dans une application 3.0, Microsoft recommande que chaque application migre vers Endpoint Routing si possible. Pour cela il faut plutot utiliser le modèle : "Application web (Model-View-Controller)" ou dotnet new mvc.

Le fichier le plus important de notre projet est sans aucun doute le fichier Startup.cs, il configure l’ensemble de l’application

Le fichier Program.cs, instancie un serveur web en quelque sorte et donne en paramètre le fichier Startup.cs (pour simplifier). C’est dans ce fichier que vous configurerez par exemple l’authentification, l’utilisation de session, l’utilisation de SignalR (websockets), l’utilisation d’identity (l’ensemble du mécanisme de connexion/inscription) et bien d’autre choses.

Le fichier *.csproj qui est le fichier du projet à proprement dit, il contient les références des paquets Nuget et c’est à peu près tout.

Quant aux fichiers appsettings.json (e t appsettings.Development.json), ils contiendront la configuration de votre application, comme par exemple des clés d’API ou les chaînes de connexion à votre base de données.

Maintenant toujours dans notre shell nous allons taper la commande dotnet run, .Net Core devrais télécharger quelques librairies et éxecuter notre projet, si vous avez un problème de certificat, c’est qu’il tante d’écouter en mode SSL, il faudra donc supprimer la ligne https://localhost:5001 dans launchSettings.json .

Quelques Commandes Utiles

  • dotnet new console : Créer un nouveau projet de type console
  • dotnet build : Compile votre projet
  • dotnet restore : Restore les paquets Nuget
  • dotnet watch run : Execute votre projet et le relance s’il détecte une modif
  • dotnet publish -c Release : Publie votre projet en mode Release

Dans Views\Home on trouve les fichiers cshtml Dans Views\Shared on trouve les liens d'entête sont dans Shared Le pied de page avec la balise