Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Les projet ASP.Net Core 2

Poster un commentaire

J’ai écris une série d’articles sur la prise en main d’un projet ASP.NET Core 1 ici:

https://amethyste16.wordpress.com/2016/11/30/les-projets-asp-net-core/

Depuis la version 2 est arrivée et il n’est pas inutile de faire une petite mise à jour. Tout n’ayant pas changé, je vais me contenter des nouveautés et d’ajouter des compléments techniques.

Pour la suite je résumerai le nom de l’outil en C2.

Installation

C2 a été annoncé l’été dernier. D’une façon générale on peut suivre les annonces concernant ASPNET sur ce github:

https://github.com/aspnet/Announcements/issues?q=is%3Aopen+is%3Aissue+label%3A2.0.0

Pour installer le SDK il faut aller ici:

https://www.microsoft.com/net/learn/get-started/windows

C2 n’est pas supporté par Visual Studio 2015.

Projets C2

Structure du projet

Je vais ici essentiellement m’intéresser aux projets Web:

On fait OK:

Sélectionnons le projet Web Application MVC, puis OK:

En apparence rien a changé depuis C1, mais en fait beaucoup de choses sont différentes.

Tout d’abord MS est revenu au projets en *.csproj à la place de project.json. On a même un menu contextuel pour éditer directement le projet:

 

Ce n’est pas tout, regardez le Nuget par défaut:

Il est nettement plus épuré.

Le petit miracle tient en une nouveauté: les Meta Packages

Un méta package est un package de package. Il suffit de le charger pour qu’automatiquement les packages liés le soient aussi.

Le meta-package qui nous intéresse ici est Microsoft.AspNetCore.All, examinons ses dépendances:

On s’épargne les galères des changements de version, par contre le méta package n’est pas enregistré au niveau projet, mais au niveau serveur. Donc pour déployer une appli C2 il faudra penser à déployer C2 également.

 

Une appli C2 est par essence une appli console, jetons un coup d’œil rapide dans le Main:


   public class Program
   {
      public static void Main(string[] args)
      {
         BuildWebHost(args).Run();
      }

      public static IWebHost BuildWebHost(string[] args) =>
         WebHost.CreateDefaultBuilder(args)
         .UseStartup<Startup>()
         .Build();
}

La console instancie un IWebHost, c’est à dire un hôte pour héberger l’appli C2 et déclare que le fichier Startup détient les informations de configuration et de déclaration de la pile OWIN.

Si le sujet vous intéresse, voici une bonne lecture:

https://docs.microsoft.com/fr-fr/aspnet/core/fundamentals/hosting?tabs=aspnetcore2x

Il n’est pas sans intérêt de regarder le source de WebHost.CreateDefaultBuilder:

https://raw.githubusercontent.com/aspnet/MetaPackages/dev/src/Microsoft.AspNetCore/WebHost.cs

On y trouve beaucoup de choses, en particulier ceci:

En (1) la déclaration du fichier de configuration. Il y a un chapitre dédié plus loin. C’est intéressant car cela montre que l’on peut y accéder très tôt dans le cycle de vie de l’application et que l’on a pas besoin de faire soi-même la déclaration.

En (2) on appelle les variables d’environnement et en (4) l’intégration IIS comme reverse proxy. Je reviens la dessus un peu plus loin.

En (4) on voit que par défaut on configure deux outils de logs:

  1. AddConsole
  2. AddDebug

AddConsole logue dans la console et AddDebug ne logue que si l’on est en mode DEBUG. On les configure dans le fichier de config:

La console peut avoir un impact sur les performances, je conseille donc de le désactiver en production.

 

Examinons maintenant Startup:


public class Startup
{
   public Startup(IConfiguration configuration)
   {
      Configuration = configuration;
   }

   public IConfiguration Configuration { get; }

   // This method gets called by the runtime. Use this method to add services to the container.
   public void ConfigureServices(IServiceCollection services)
   {
      services.AddMvc();
   }

   // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
   public void Configure(IApplicationBuilder app, IHostingEnvironment env)
   {
   if (env.IsDevelopment())
   {
      app.UseBrowserLink();
      app.UseDeveloperExceptionPage();
   }
   else
   {
      app.UseExceptionHandler("/Home/Error");
   }

   app.UseStaticFiles();

   app.UseMvc(routes =>
   {
      routes.MapRoute(
      name: "default",
      template: "{controller=Home}/{action=Index}/{id?}");
   });
  }
}

 

Startup exécute tout le code qui a besoin d’être lancé au démarrage, c’est le global.asax d’un projet Core.

Le premier changement est que la configuration est injectée directement dans le contrôleur. L’hôte commence par exécuter ConfigureServices qui est là pour fournir des paramétrages des différents services utilisés dans le projet. Le plus souvent c’est l’injection de dépendance. Par défaut c’est juste le pipeline MVC comme on le voit dans le code.

 

La pile OWIN est configurée dans Configure. On peut lui adjoindre d’autres services comme ILogger par exemple.

La pile OWIN a pour but de traiter les requêtes reçues.

Les modules de traitement s’appellent des middlewares dans le monde OWIN. On les ajoutent à la pile depuis une des méthodes d’extensions liées à IApplicationBuilder.

Il existe des méthodes en UseXXX et des méthodes en RunXXXX. Ces dernières sont des méthodes terminales, c’est à dire que le traitement s’arrête là.

Les méthodes en UseXXX passent la main au middleware suivant dans la pile.

Un middleware ressemble assez à un module de l’ancien monde. Ces outils remplissent une fonction similaire. Parmi les différences notables remarquons que l’on peut contrôler l’ordre d’exécution des middlewares et surtout sont indépendants de l’hôte.

Core (et OWIN) est justement une évolution dans laquelle on sépare l’hôte de la pile d’exécution. C’est à dire que l’on est plus du tout liés à IIS, mais n’importe quel hôte peut faire l’affaire.

C’est bien entendu le multiplateforme qui est visé avec l’architecture OWIN.

 

Core dit aussi adieu à Cassini, maintenant c’est Kestrel, un serveur web multi-plateforme basé sur la librairie open-source libuv.

C’est un point important à comprendre. Core a vocation à être multiplateforme et donc de fonctionner dans n’importe quel serveur Web, plus uniquement IIS. La question pour Microsoft est de faire en sorte que le code de lancement (Main et Startup) soit indépendant du choix du serveur Web.

Le plus simple est donc de la faire toujours démarrer dans l’environnement Kestrel et monter les autres serveurs en reverse proxy comme expliqué ici:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/?tabs=aspnetcore2x

Un reverse proxy est un proxy qui reçoit les requêtes émises depuis Internet et les redirigent vers Kestrel. On ne peut donc plus exécuter une appli directement dans IIS, Nginx, Apache…

On pourrait évidement ne passer que par Kestrel. Les bonnes pratiques le décourage si le site est exposé à Internet. La raison est que cet outil n’est pas forcément aussi bien sécurisé que les serveurs Web poids lourds et leurs années d’expérience. On peut lire ue discussion ici:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?tabs=aspnetcore2x

 

Revenons un instant sur la méthode ConfigureServices. On peut configurer MVC avec AddMvc().

Cette méthode encapsule tout un tas de déclarations que l’on devait ajouter soi-même auparavant. Regardons rapidement le code de cette méthode que l’on trouve directement dans Github:

https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs

On économise pas mal de saisie. On remarque aussi que si l’application n’est pas une application MVC, mais Web Api, alors on a peut être pas besoin de s’encombrer de choses comme Razor , les Tag Helper, le gestionnaire de vues…. Justement on remarque une méthode appelée juste avant tout ça: AddMvcCore() (premier encadré).

Pour une Web Api on peut donc remplacer AddMvc() par AddMvcCore(). Notons toutefois que le formateur JSON ne fait pas partie des services configurés ce qui est dommage. Il suffit de le rajouter à la main et du coup:


services.AddMvcCore()
   .AddJsonFormatters();

On a  un pipeline plus léger.

Le répertoire wwwroot

Le nom de ce répertoire est identique à un sous-répertoire d’Inetpub dans lequel on déploie traditionnellement une appli IIS. Ce n’est pas un hasard, c’est le nouveau répertoire racine d’un projet Core.

Répertoire racine au sens HTML. On y place les fichiers statiques, mais les fichiers ASP.NET comme *.cshtml sont placés aux emplacements habituels.

On peut aussi y placer le fichier web.config qui ne contient plus que les configurations du serveur web.

Vous vérifierez facilement que si le fichier est déplacé en dehors de wwwroot ou un de ses répertoires, ils n’est plus accessible. Si vous avez besoin de référencer un des artefacts de wwwroot depuis une vue par exemple, la syntaxe sera la suivante:

Juste un tilde (~).

Exécution d’un projet C2

Le fichier launchSettings.json est un fichier important.

 

C’est le fichier qui donne à Kestrel toutes les informations nécessaires au lancement de l’application. Voici à quoi ressemble le fichier par défaut:

La première partie rassemble des paramétrages destinés à IIS.

Les deux parties suivantes de la section profiles correspondent à deux environnements d’exécution. On peut basculer de l’un vers l’autre depuis VS:

Ca correspond à quoi?

Regardons la propriété commandName qui indique comment le projet doit démarrer:

  • IISExpress indique que l’on lance l’appli dans IIS Express
  • Project indique que l’on lance l’appli depuis Kestrel uniquement
    Au lancement on voit une fenêtre de commande s’ouvrir.

Une autre option est IIS qui lance l’application dans IIS. Si vous allez dans IIS management vous verrez qu’une appli a été créée dans le site par défaut.

 

Note: pour que ça marche, vous pourriez devoir faire un peu de ménage dans le fichier de configuration comme indiqué ici:

https://github.com/dotnet/cli/issues/7741

 

ApplicationUrl est l’url de base pour lancer l’application. En général elle suffit. Parfois on a besoin de compléter le chemin. On le fait avec launchUrl. Par exemple dans un projet avec Swagger on pourrait voir ceci pour démarrer directement sur la page de doc:

L’application sera lancée sur l’url: http://localhost:6563/swagger pour le profil Chlorophyle.

 

On peut éditer le fichier directement ou bien le manipuler avec VS dans les propriétés du projet:

 

  • Le nom du profile est en (1). On peut en créer un nouveau avec le bouton NEW.
  • En (2) on sélectionne une valeur pour commandName.
  • En (3) vous reconnaissez launchUrl.
  • Enfin en (4) on sélectionne applicationUrl.

 

Si vous avez des difficultés pour lancer le projet avec un de ces profils, deux choses à faire:

  1. Lire les Events Logs si vous êtes sous Windows
  2. Lancer manuellement l’appli avec la ligne de commande:

dotnet run <mon appli>.dll

Qui en général donne plus de détails sur ce qui ne va pas.

Fichier de configuration

On n’a plus de web.config, mais on peut tout de même fournir des paramètres à une application via le fichier appsettings.json. Il existe en différentes couleurs selon le contexte d’exécution:

L’environnement est définit par la variable d’environnement ASPNETCORE_ENVIRONMENT que l’on peut placer dans le profil de lancement:

 

Donc rien à voir avec les modes de compilation Release, Debug…

Comment lire un tel fichier depuis le code?

Voici un exemple:


{
   "AuthenticationInfos": {
   "SecretKey": "...",
   "Issuer": "",
   "Audience": ""
},
"ConnectionStrings": {
   "DefaultConnection": "Server=tcp:..."
}
}

On trouve deux sections:

  1. AuthenticationInfos
  2. ConnectionStrings

 

Avec RC2 les choses sont plus simples que pour RC1, il y a moins de code à écrire, plus précisément rien de spécial à déclarer en plus du code par défaut.

La première section est typiquement lue dans Startup. On dispose déjà d’un IConfiguration, il suffira donc d’écrire:


IConfigurationSection section = Configuration.GetSection("AuthenticationInfos");
string key = section.GetValue<string>("SecretKey");

La deuxième section sera typiquement lue dans un service ou un référentiel. On a juste besoin de récupérer un IOptions, le mieux étant de l’injecter dans le constructeur:


readonly IOptions<AuthenticationInfos> _authenticationInfos;

public LoginsService(IOptions<AuthenticationInfos> authenticationInfos)
{
_authenticationInfos = authenticationInfos;
}

Vous devinez sans difficultés la déclaration correspondant à l’objet AuthenticationInfos.

En lecture il suffit d’invoquer:

_authenticationInfos.Value

 

Qui retourne une instance de AuthenticationInfos.

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion /  Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion /  Changer )

w

Connexion à %s