La structure d’une solution ASP.NET Core 1.0
Une solution ASP.NET Core 1.0 est la base d’un projet Web utilisant les technologies Microsoft. Elle permet rapidement de déployer un site et de structurer le code utilisé pour faire fonctionner l’application. Une solution comporte à la fois le code côté serveur et le code côté client, tout en incluant des mécanismes afin de bien séparer les 2 parties. Ce chapitre va traiter des différents composants d’une solution ASP.NET Core 1.0 et expliquer leurs rôles dans la configuration ou le déploiement de l’application Web.
Les fichiers global.json et project.json
Un projet ASP.NET Core 1.0 met en œuvre une toute nouvelle philosophie de conception d’application Web chez Microsoft en s’inspirant beaucoup de l’OpenSource. Le nouveau template ressemble à ceci :
La première chose que l’on peut remarquer c’est la disposition des dossiers dans la solution. Tous les projets .NET (application Web, librairies …) se trouvent dans un dossier nommé src. Typiquement, d’autres dossiers peuvent être insérés comme tests, pour des projets de tests (unitaires, fonctionnelles …), et nous allons avoir ainsi des éléments globaux à l’ensemble de la solution et à son bon fonctionnement.
Le dernier dossier intéressant ici est le Solution Items. Ce dernier comporte un fichier de configuration pour toute la solution intitulé global.json.
L’extrait de fichier ci-dessous montre les deux sections utilisées dans le global.json :
- projects : permet de définir où se situe le code source de l’application. Par défaut, on retrouve le dossier src, mais on peut organiser cette section en fonction des éléments que l’on veut inclure dans son contrôleur de code source par exemple ;
- sdk : permet de spécifier la version de DNX que l’on veut utiliser. La version s’applique alors à l’ensemble de la solution, et donc à tous les projets inclus dans la solution pour éviter des erreurs de version au niveau du SDK.
{ "projects": [ "src", "test" ], "sdk": { "version": "1.0.0-rc1-final" } }
Le fichier project.json est également l’un des nouveaux fichiers présent dans une solution ASP.NET Core 1.0. Anciennement web.config, on retrouve ici un fichier utilisant le format JSON (JavaScript Object Notation, format de données textuelles de plus en plus populaire) permettant de décrire la configuration globale du projet.
Les différentes composantes du fichier sont :
- version : permet d’indiquer la version de l’application Web ;
- compilationOptions : regroupe toutes les options de compilation qu’il est possible d’ajouter lors de la compilation de la solution. Dans l’exemple ci-dessous, emitEntryPoint indique au compilateur qu’il existe un point d’entrée dans l’application ;
La liste des options de compilation et la documentation ne sont pas encore établies pour l’instant : http://docs.asp.net/en/latest/dnx/compilation.html
- dependencies : présent également sous le nœud References, cette section définit toutes les références, à des librairies .NET ou externes, côté serveur. On retrouve ainsi des libraires comme Entity Framework, pour la gestion de la base de données ou encore AspNet.Mvc pour l’utilisation du pattern MVC dans l’application Web.
Lorsque vous modifiez cette section, Visual Studio vous aide avec de l’auto complétion. Cela est possible grâce à l’ouverture, de la part de Microsoft, de ses librairies à l’OpenSource qui sont maintenant sur GitHub. De ce fait, vous avez accès à chacune des librairies disponibles sur GitHub qui sont compatibles ASP.NET Core 1.0. Les packages NuGet sont toujours disponibles dans les dépendances. D’ailleurs, si une dépendance est ajoutée dans la section dependencies, Visual Studio va automatiquement télécharger le package, et l’inclure dans le projet sous le nœud References.
- commands : comme indiqué plus haut, ASP.NET Core 1.0 est résolument tourné vers l’OpenSource. La section commands permet ainsi de configurer des commandes qui vont pouvoir ensuite être lancées depuis un outil en ligne de commande. On parle ici de lancer le site web, effectuer des tests, faire des migrations de base de données …
Cette section trouve tout son sens sur les autres plateformes telles que Linux ou Mac, car il est maintenant possible de lancer le site web avec une simple commande, sans utiliser Visual Studio.
- frameworks : cette section définit quel framework votre projet va cibler. Sur notre exemple, nous ciblons à la fois le framework .NET complet (via la sous-section « dnx451 »: { }) mais aussi .NET Core (via la seconde section « dnxcore50 »: { }). L’intérêt de ces sous-sections est que l’on peut intégrer des dépendances différentes selon le framework. Ainsi, pendant le développement de l’application web, Visual Studio va afficher automatiquement si une API est disponible dans les différents frameworks qui sont ciblés.
- exclude : comme son nom l’indique, cette section permet d’ajouter des fichiers ou des dossiers qui ne seront pas inclus dans la build. Dans notre exemple, nous évitons d’inclure le dossier wwwroot (que nous allons étudier plus tard) car il ne contient que des fichiers statiques, inutile donc de le compiler.publishExclude : dans la continuité de la précédente section,
- publishExclude permet de spécifier les fichiers qui ne seront pas inclus dans un processus de déploiement sur un serveur (en production par exemple).
- scripts : cette dernière partie permet de lancer des processus JavaScript afin d’effectuer certaines actions à des moments bien précis. Dans notre exemple, nous lançons nos scripts pendant la phase prepublish, c’est-à-dire avant la publication du site sur un serveur. On retrouve d’autres phases telles que prebuild, postbuild, prepack, postpack, prepublish, postpublish, prerestore, postrestore et prepare.
{ "version": "1.0.0-*", "compilationOptions": { "emitEntryPoint": true }, "dependencies": { "Microsoft.AspNet.IISPlatformHandler": "1.0.0-rc1-final", "Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final" }, "commands": { "web": "Microsoft.AspNet.Server.Kestrel" }, "frameworks": { "dnx451": { }, "dnxcore50": { } }, "exclude": [ "wwwroot", "node_modules" ], "publishExclude": [ "**.user", "**.vspscc" ], "scripts": { "prepublish": [ "npm install", "bower install" ] } }
Le fichier.json est susceptible d’être encore enrichi par d’autres options, mais possède déjà beaucoup de fonctionnalités. La documentation sur GitHub permet d’approfondir le sujet : https://github.com/aspnet/Home/wiki/Project.json-file.
Les propriétés de l’application Web
La structure d’une solution ASP.NET Core 1.0 inclus un projet Web unique permettant de poser les premières bases du site. Le chapitre précédant évoquait la possibilité de configurer le projet via le fichier project.json, mais il est également possible de configurer le projet via ses propriétés. Pour ce faire, faites un clic-droit sur le projet Web, puis Propriétés. La première page de paramétrage concerne l’application en elle-même. Trois options sont présentes :
- Espace de noms par défaut : permet de spécifier l’espace par défaut de l’application Web. En général, c’est la racine du projet ;
- Version du SDK DNX de la solution : permet de spécifier la version du SDK utilisée. Cette option est également dans le fichier global.json présenté dans le chapitre précédant ;
- Racine web : dossier racine du projet Web une fois déployé. En général, c’est le dossier wwwroot (présenté dans la section suivante), également paramétrable dans le fichier project.json sous l’option webroot.
Le deuxième onglet de paramétrage concerne la compilation du projet. Cette page propose plusieurs paramétrages comme :
- Configuration : permet de spécifier le mode de compilation du projet, tel que Debug, Release … ;
- Plateforme : indique sur quelle plateforme la compilation doit se baser.
De base, la compilation ne produit aucune DLL afin de rendre le tout cross-platform. Cependant, il est possible de spécifier explicitement la génération des packages et des DLLs du projet lors de la compilation grâce à la première option. La deuxième option permet à Visual Studio de générer les fichiers TypeScript en fichier JavaScript directement lors de la génération. Dans le cas contraire, la génération doit se faire manuellement.
Le troisième onglet du paramétrage concerne le débogage de l’application. L’écran ci-dessus permet de configurer les options de débogage de l’application. On retrouve des options liées au profil de développement paramétrable suivant les commandes spécifiées dans le fichier project.json. Ceci permet facilement de changer d’environnement de développement (développement pré-production, production …) tout en spécifiant une URL de lancement si besoin.
L’utilisation d’un runtime spécifique peut s’avérer utile si le projet nécessite une version du SDK plus stable, ou au contraire plus avancée. Par défaut, cette option reprend la valeur indiquée dans l’onglet Application. Les variables d’environnements permettent de définir des valeurs qui seront communes à l’ensemble de la solution. Au sein du template de base fournit par Visual Studio, la variable Hosting:Environnement permet de définir sur quel environnement le projet se trouve, modifiant ainsi le comportement de l’application suivant si on se trouve en environnement de production ou de développement.
Le code ci-dessous modifie le paramétrage de l’application uniquement si on se trouve en environnement de production. La méthode IsDevelopment() se base sur une variable d’environnement pour déterminer si on est en phase de développement ou non.
if (env.IsDevelopment()) { app.UseBrowserLink(); app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); }
La dernière section de cet onglet concerne le paramétrage du serveur Web. Plusieurs options sont disponibles :
- URL de l’application : permet de définir l’URL afin que l’utilisateur puisse accéder à l’application Web ;
- Activer SSL : permet d’activer SSL (Secure Socket Layer, mode sécurisé de connexion aux sites web) ;
- Activer l’authentification anonyme : indique si le visiteur du site web peut accéder au contenu sans authentification particulière ;
- Activer l’authentification Windows : indique si le visiteur doit s’authentifier via une authentification Windows au préalable.
Les mêmes paramétrages sont disponibles via un fichier de configuration placé sous le nœud Properties : c’est le launchSettings.json.
{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:28821/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "Hosting:Environment": "Development" } }, "web": { "commandName": "web", "environmentVariables": { "Hosting:Environment": "Development" } } } }
Ce fichier permet de configurer le projet Web sans passer par Visual Studio, et permet ainsi la compilation d’un projet ASP.NET Core 1.0 sous d’autres environnements que Windows.
Le dossier wwwroot
Dans les précédentes versions, la racine du site web était typiquement la racine du projet web de la solution Visual Studio. Cette logique reste très spécifique à ASP.NET, et les ressources statiques comme les images, les styles ou encore les scripts étaient mélangés avec les autres fichiers ASP.NET comme les contrôleurs. Cela pose tout d’abord un problème de sécurité, car de ce fait le client pourrait malencontreusement accéder au web.config via une requête mal configurée. Ensuite, cette configuration structurelle complexifiait le déploiement sur les serveurs : quels fichiers dois-je déployer ? Comment sécuriser l’accès aux fichiers de déploiement en production et en développement ?
Ce couplage fort ne permettait pas une ouverture à l’OpenSource, et l’équipe en charge du développement d’ASP.NET a du complètement repensé la structure du projet Web pour amener de l’ouverture au sein de la technologie Web de Microsoft. C’est ainsi que le dossier wwwroot a été introduit. Pour faire simple, ce dossier est la nouvelle racine du projet, et ne contient que les fichiers statiques du site web.
La règle est simple : tout fichier qui ne se trouve pas dans le dossier wwwroot ne sera pas accessible à partir du site web. Il est donc important d’y placer tous les styles (fichiers CSS), tous les scripts, toutes les images, toutes les librairies JavaScripts … auxquels vous avez besoin d’accéder dans le site
Cela comporte plusieurs avantages :
- Inutile de créer des règles spéciales pour l’accès aux fichiers du site ;
- Découplage entre les fichiers statiques et les fichiers ASP.NET ;
- Simplification des déploiements.
Le fichier web.config n’a pas totalement disparu d’un projet Web ASP.NET, et se retrouve au final dans ce dossier wwwroot.
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/> </handlers> <httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false" startupTimeLimit="3600"/> </system.webServer> </configuration>
Avant la beta7 d’ASP.NET Core 1.0, le lancement d’un projet Web sur IIS se faisait grâce au package Microsoft.AspNet.Server.IIS via un composant appelé Helios. Ce dernier constituait un second hôte DNX, avant que l’hôte IIS appelé Kestrel prenne le dessus lors du lancement de l’application.
Depuis la beta8, héberger une application ASP.NET Core 1.0 sera possible grâce au module HttpPlatformHandler d’IIS configurer via le web.config du projet. Etant un module natif d’IIS, cela permet d’unifier et de simplifier l’hébergement sur le serveur.
Le module HttpPlatformHandler doit être installé sur votre serveur pour faire fonctionner une application ASP.NET Core 1.0 et n’est utilisable qu’avec IIS 8 ou supérieur.
Lister et commenter chaque option ne fait pas l’objet de cet écrit, en revanche la documentation est très fournit à ce sujet : http://www.iis.net/learn/extensions/httpplatformhandler/httpplatformhandler-configuration-reference.
Les variables DNX_PATH et DNX_ARGS sont automatiquement replacées par Visual Studio lors du déploiement :
- DNX_PATH devient ‘…\approot\web.cmd’ afin de spécifier le processus de démarrage de l’application web (configurable depuis le json, dans la section commands).
- DNX_ARGS concerne les arguments passés au runtime DNX si besoin.
Enfin, on retrouve un fichier intitulé _references.js permettant d’avoir l’IntelliSense de Visual Studio dans tous les fichiers JavaScript qui sont référencés dans ce fichier. Les caractéristiques de ce fichier sont :
- C’est un fichier global, c’est-à-dire qu’il n’est plus nécessaire d’inclure les mêmes scripts dans chacun des fichiers JavaScript où c’est nécessaire, il suffit de le référencer dans le fichier js et le tour est joué ;
- Les scripts sont inclus via un triple slash
/// <reference path="modernizr-2.6.2.js" /> /// <reference path="jquery-1.10.2.js" /> /// <reference path="bootstrap.js" /> /// <reference path="respond.js" />
- Visual Studio ajoute automatiquement les nouveaux scripts dans ce fichier grâce à l’instruction /// <autosync enabled= »true » /> insérée tout en haut du fichier javascript. Dans la même idée, Visual Studio ajoute quelques fonctionnalités qui aident à gérer le fichier d’Intellisense Javascript plus facilement.
Le fichier appsettings.json
ASP.NET Core 1.0 supporte une multitude de façon de configurer un projet Web. Lorsqu’on parle de configuration, on parle ici de la chaîne de connexion à la base de données, des identifiants administrateurs, du niveau de log … Afin également de s’ouvrir à l’OpenSource, Microsoft a dû inventer un système capable de supporter plusieurs formats afin de satisfaire au plus grand nombre de développeur. Le web.config n’était donc plus d’actualité, l’équipe en charge du développement d’ASP.NET Core 1.0 a développé une API uniquement destiné à gérer des fichiers de configuration pour le projet : c’est l’API Configuration. Dans cette partie, nous allons simplement explorer un exemple de fichier exploitable par cette API.
Un format en particulier est le plus utilisé de manière général pour la configuration d’un projet ASP.NET Core 1.0 : le JSON. C’est ainsi qu’il existe, dans le template Visual Studio de base, un fichier nommé appsettings.json permettant de stocker des informations relatives à divers aspects configurables du projet.
{ "Data": { "DefaultConnection": { "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=_CHANGE_ME;Trusted_Connection=True;" } }, "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Verbose", "System": "Information", "Microsoft": "Information" } } }
Les données sont ordonnées hiérarchiquement permettant de classer les informations telles un arbre. Dans le code, ces informations seront accessibles avec des clés, qui peuvent être composées pour former des chemins : Data:DefaultConnection:ConnectionString.
Il ne faut jamais stocker des mots de passe ou des données sensibles sur le projet dans les fichiers de configuration, ou même dans le code source. Il en va de même pour les informations de production qui doivent rester en dehors du projet pour éviter ainsi de se retrouver dans le contrôleur de code source.
Chaque catégorie doit comporter un type de donnée bien spécifique. Par exemple, il ne faut pas mélanger les informations relatives à la chaîne de connexion avec les informations relatives aux logs du système. Ceci est très important notamment lorsque ces informations vont se retrouver dans le code sous forme de classe.
Le pattern Options permet de stocker et de classer toutes les informations de configuration qui sont spécifiées dans les fichiers tels que appsettings.json. Il spécifie simplement que certaines classes sont considérées comme des classes d’options, et peuvent être ainsi injectées dans nos contrôleurs très facilement afin d’accéder aux informations dont l’application a besoin. Ci-dessous un exemple de configuration :
"Data": { "Option1": "Ceci est une première option", "Option2": "Ceci est une deuxième option" },
Et ici sa représentation en classe grâce au pattern Options :
public class Data { public string Option1 { get; set; } public string Option2 { get; set; } }
Par la suite, une fois que ces options seront créées et renseignées dans un conteneur de service, il sera possible d’utiliser cette classe d’option via l’injection de dépendances dans nos contrôleurs.
public class HomeController : Controller { public HomeController(IOptions<MyOptions> optionsAccessor) { Options = optionsAccessor.Options; } MyOptions Options { get; } public IActionResult Index() { return View(Options); } }
Conclusion
La nouvelle structure d’un projet ASP.NET Core 1 repense notre façon de voir une application Web Microsoft et s’ouvre à l’OpenSource, notamment dans le domaine de la configuration et du découpage des couches (front-end, back-end …). Il nous faut cependant prendre du recul car toute la stack technique n’est pas encore terminé : Entity Framework 7 est encore en pré-release et beaucoup de modification sont encore à venir dans les processus et de déploiement (notamment hors environnement Windows).