Présentation du framework ASP.NET Core

Kevin Sousselier

Le but de ce tutoriel est de présenter le framework ASP.NET Core.

Commentez

- Titre : Présentation du framework ASP.NET Core

- Auteur : Kevin Sousselier

- Parution : 17 octobre 2016

- Mise à jour : 28 janvier 2017

- Licence : Copyright ® 2017 Kevin Sousselier. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. ASP.NET Core

- I-A. Open source

- I-B. Multi plateforme

- I-C. Modularité

- I-D. Indépendant de l'éditeur

- I-E. Cloud ready

- II. Développement

- III. Architecture d'une application ASP.NET / .NET et ASP.NET Core / .NET Core

- IV. Structure d'une application ASP.NET MVC et ASP.NET Core MVC

- V. Gestion des packages

- VI. Middlewares

- VII. Paramètres

- VII-A. Accès aux paramètres

- VII-B. Paramètres en fonction des environnements

- VIII. Bundling et minification

- IX. Injection de dépendance

- X. Tags Helpers

- XI. Remerciements

I - ASP.NET Core

ASP.NET Core est le nouveau framework open source et multiplateforme de Microsoft, pour développer des applications web. Il fonctionne aussi bien avec .NET Core que .NET 4.6.

Il ne remplace pas ASP.NET 4.6 qui reste à l'heure actuelle la plateforme la plus mature. Il vient se mettre en parallèle avec des spécificités qui lui sont propres.

I-A - Open source

ASP.NET Core est un framework open source. Ses sources et sa documentation sont disponibles sur GitHub respectivement aux adresses https://github.com/aspnet/Home et https://github.com/aspnet/Docs. Toute personne peut y contribuer.

I-B - Multi plateforme

ASP.NET Core permet de créer et exécuter des applications sur Windows, Mac et Linux.

I-C - Modularité

Par rapport à ASP.NET qui a maintenant 15 ans d'existence, ASP.NET Core propose une nouvelle architecture reposant sur un ensemble de packages gérés via NuGet rendant le framework plus modulaire, léger et clair. Notamment, les parties web UI et web API partagent les mêmes contrôleurs.

I-D - Indépendant de l'éditeur

Vous n'êtes ni obligé d'utiliser Visual Studio pour le développement ni IIS pour l'hébergement. N'importe quel éditeur associé avec dotnet Command Line Interface permet de créer, générer et exécuter votre application.

De même, n'importe quel serveur compatible OWIN peut héberger votre application.

I-E - Cloud ready

Le système de configuration d'une application ASP.NET Core est optimisé pour un hébergement dans le cloud.

Toutes ces modifications ont des impacts au niveau du code, mais ne vous inquiétez pas. Si vous êtes familiers de l'environnement ASP.NET classique, vous ne serez pas perdus et vous allez vite vous y habituer.

II - Développement

Pour développer une application ASP.NET Core, vous pouvez opter pour Visual Studio ou bien un autre éditeur tel que Visual Studio Code.

Dans le premier cas, il faut utiliser Visual Studio 2015 Update 3 (par exemple la licence Community qui est gratuite) ou version supérieure.

Trois types de projets vous seront alors proposés :

- Empty : un projet vide c'est-à-dire sans code d'exemple et avec le strict minimum ;

- Web API : un projet avec un exemple de contrôleur pour un service HTTP RESTFul ;

- Web application : un projet avec un exemple de vues et de contrôleurs ASP.NET Core MVC.

Les Web Forms ne sont pas supportés.

Si vous partez avec Visual Studio Code, vous devez installer séparément .NET Core et générer votre code en ligne de commande.

Pour bénéficier de templates de projet comme avec Visual Studio, vous pouvez utiliser le générateur Yeoman.

Visual Studio Code est gratuit et a l'avantage d'être plus léger et d'être disponible sous Mac et Linux.

III - Architecture d'une application ASP.NET / .NET et ASP.NET Core / .NET Core

ASP.NET Core peut fonctionner avec .NET Core ou bien .NET. Si vous choisissez d'utiliser le framework .NET vous avez accès à l'intégralité des fonctionnalités et bibliothèques fournies par .NET. Certaines de ces fonctionnalités ne sont pas encore supportées par .NET Core. En contrepartie, vous perdez des avantages tels que le multiplateforme ou la modularité.

Dans le cas où nous avons d'un côté une application ASP.NET / .NET et de l'autre ASP.NET Core / .NET Core les architectures ne sont pas les mêmes :

ASP.NET / .NET

ASP.NET Core / .NET Core

Bibliothèques de base

.NET Framework Class Library

CoreFX Class Library

Environnement d'exécution

Common Language Runtime

CoreCLR

IV - Structure d'une application ASP.NET MVC et ASP.NET Core MVC

Sur l'image en dessous, vous pouvez voir à gauche l'arborescence d'une application ASP.NET MVC et à droite celle d'une application ASP.NET Core MVC Web Application, toutes les deux créées avec Visual Studio 2017 :

Voici les principales différences que nous pouvons constater :

- l'ajout des fichiers Program.cs et Startup.cs à la place des fichiers Global.asax, RouteConfig.cs et FilterConfig.cs pour les fonctions d'initialisation et de configuration de l'application ;

- l'ajout d'un fichier bundleconfig.json pour la gestion des bundles et la disparition du fichier BundleConfig.cs ;

- l'ajout du fichier appsettings.json pour les paramètres. Le fichier web.config est toujours présent, mais il n'est plus utilisé pour les paramètres de l'application ;

- l'ajout du fichier bower.json et .bowerrc pour la gestion des packages Bower ;

- l'ajout du répertoire wwwroot pour tous les fichiers statiques (favicon.ico, fichiers JavaScript, fichiers CSS…)

V - Gestion des packages

Dans ASP.NET Core les packages serveur sont gérés par Nuget qui était déjà utilisé dans les autres versions du framework .NET.

Par contre, pour les packages web, ASP.NET Core utilise Bower, car c'est un gestionnaire bien plus fourni que Nuget pour tout ce qui est librairie JavaScript et CSS.

VI - Middlewares

Les middlewares sont des composants logiciels qui vont traiter les requêtes HTTP à travers un pipeline. Dans ASP.NET classique, toute cette logique est répartie entre les modules (classes qui implémentent IHttpModule), les handlers (classes qui implémentent IHttpHandler), le fichier Global.asax.cs et le fichier web.config. Le tout étant orchestré par la série d'évènements du cycle de vie d'une application ASP.NET.

Les middlewares peuvent faire les mêmes choses que les modules et les handlers, mais en plus :

- ils sont plus simples d'utilisation (nous n'avons plus tous les fichiers précédemment cités ni la série d'évènements) ;

- la configuration se fait dans le code (méthode Configure du fichier Startup.cs) ;

- le pipeline permet de répartir les requêtes suivant l'URL, mais également l'entête, les paramètres…

Par exemple, si nous voulons renvoyer une chaîne de caractères en réponse à toutes requêtes HTTP, il faut modifier la méthode Configure :

Startup.cs

public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Application ASP.NET Core");
});
}

Voici le pipeline du template ASP.NET de Yeoman :

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}

app.UseStaticFiles();

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

Dans ce pipeline, nous retrouvons un certain nombre de middlewares classiques dans une application web qui vont gérer les exceptions, accéder au fichier statique ou configurer la table de routage.

VII - Paramètres

VII-A - Accès aux paramètres

Dans ASP.NET, les paramètres de configuration sont stockés dans le fichier web.config ou bien dans un fichier XML séparé qui est lui-même référencé par le fichier web.config.

Dans ASP.NET Core, les paramètres sont placés dans des fichiers JSON, XML ou INI. Par exemple dans le template de base de Visual Studio, ils sont stockés dans un fichier JSON nommé appsettings.json :

appsettings.json

{
"ApplicationInsights": {
"InstrumentationKey": ""
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"Section1": {
"Parameter1": "Value",
"Parameter2": true,
"Parameter3": 3
}
}

Pour accéder aux paramètres de ce fichier, nous allons créer une classe Section1 ayant pour chaque membre une propriété correspondante :

Section1.cs

public class Section1
{
public string Parameter1 { get; set; }
public bool Parameter2 { get; set; }
public int Parameter3 { get; set; }
}

Dans la classe Startup, nous allons choisir le fichier de configuration à charger et l'associer à notre classe Section1 :

Startup.cs

public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) // Chargement du fichier
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) // Chargement du fichier de                                  surcharge en fonction de l'environnement
.AddEnvironmentVariables();

if (env.IsDevelopment())
{
builder.AddApplicationInsightsSettings(developerMode: true);
}
Configuration = builder.Build();
}

public IConfigurationRoot Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
services.Configure<Section1>(Configuration.GetSection("Section1")); // Association avec la classe Section1

services.AddApplicationInsightsTelemetry(Configuration);

services.AddMvc();
}
}

Nous pouvons ensuite accéder aux paramètres par exemple dans les contrôleurs avec le mécanisme d'injection de dépendance :

public class HomeController : Controller
{
private Section1 _section1;

public HomeController(IOptions<Section1> section1)
{
_section1 = section1.Value;
}
}

VII-B - Paramètres en fonction des environnements

Dans ASP.NET classique, nous utilisons des fichiers de transformation pour gérer les paramètres du fichier web.config en fonctions des environnements. À la place, ASP.NET Core utilise un système de surcharge du fichier de paramètres en fonction de l'environnement.

Si nous voulons redéfinir les valeurs de la section 1 pour l'environnement development, nous devons créer un fichier appsettings.development.json :

appsettings.development.json

{
"Section1": {
"Parameter1": "ValueDev",
"Parameter2": false,
"Parameter3": 30
}
}

Ces nouvelles valeurs vont remplacer les anciennes si nous nous trouvons dans l'environnement development. L'environnement est défini par la variable d'environnement ASPNETCORE_ENVIRONMENT. Sous Visual Studio, vous pouvez la modifier dans l'onglet Déboguer des propriétés du projet.

VIII - Bundling et minification

Le bundling et la minification sont deux techniques pour augmenter la rapidité de chargement des pages web en réduisant le nombre et la taille des fichiers JavaScript et CSS.

Le bundling consiste à regrouper respectivement les fichiers JavaScript et CSS dans un seul fichier JavaScript et un seul fichier CSS pour limiter le nombre de requêtes au serveur.

La minification consiste à diminuer la taille des fichiers JavaScript et CSS par un ensemble de processus d'optimisation.

Dans ASP.NET classique, nous utilisons les classes de l'espace de nom System.Web.Optimization. La configuration se fait dans la classe BundleConfig en C#. Donc toute modification nécessitait de régénérer l'application.

Dans ASP.NET Core, nous allons utiliser le nouvel outil BundlerMinifer.Core qui se base sur un fichier de configuration JSON qui ne nécessite pas de régénérer l'application en cas de modification.

Vos fichiers JavaScript et CSS sont à ajouter dans le répertoire wwwroot. Vous devez ensuite les déclarer dans le fichier bundleconfig.json.

bundleconfig.json

[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js",
"wwwroot/js/monFichier1.js",
"wwwroot/js/monFichier2.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]

Lors de la première requête suivant une modification de ce fichier, l'outil va générer un fichier site.min.css et un fichier site.min.js à partir respectivement des fichiers CSS et JavaScript.

Il ne nous reste plus qu'à les incorporer dans les pages HTML :

_Layout.cshtml

<environment names="Development">
<script src="~/js/site.js" asp-append-version="true"></script>
<script src="~/js/monFichier1.js" asp-append-version="true"></script>
<script src="~/js/monFichier2.js" asp-append-version="true"></script>
</environment>
<environment names="Staging,Production">
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>

Nous pouvons faire un chargement différent en fonction de l'environnement. Soit les versions initiales pour l'environnement de développement ou bien le fichier transformé pour l'environnement de production.

IX - Injection de dépendance

L'injection de dépendance est un mécanisme qui permet d'implémenter le principe de l'inversion de contrôle. Ce principe permet de découpler les dépendances entre objets. Un des cas courants d'utilisation de l'injection de dépendance est la réalisation de tests unitaires.

Dans ASP.NET classique pour implémenter l'injection de dépendance nous devons utiliser un contrôleur de dépendance tel que Unity. ASP.NET Core propose son propre système d'injection de dépendance.

Tout abord, nous allons définir un service avec son interface et son implémentation que nous voulons injecter :

IService1.cs

public interface IService1
{
int GetRandomValue();
}

Service1.cs

public class Service1 : IService1
{
public int GetRandomValue()
{
var r = new Random();
return r.Next();
}
}

Il faut ensuite configurer le système d'injection de dépendance en associant l'interface à son implémentation.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
services.Configure<Section1>(Configuration.GetSection("Section1"));

services.AddTransient<IService1, Service1>(); // Déclaration de l'interface et de son implémentation

services.AddApplicationInsightsTelemetry(Configuration);

services.AddMvc();
}

Nous pouvons maintenant accéder à ce service dans les contrôleurs :

HomeController.cs

public class HomeController : Controller
{
private IService1 _service1;

public HomeController(IService1 service1)
{
_service1 = service1;
}

public IActionResult Index()
{
int i = _service1.GetRandomValue();
return View();
}
}

X - Tags Helpers

Les Tags Helpers sont une fonctionnalité d'ASP.NET Core MVC qui permettent de créer des éléments HTML dans un fichier Razor. La syntaxe est proche du HTML et ne nécessite aucun code C#. Ils remplacent les HTML Helpers d'ASP.NET MVC dont la syntaxe en C# était parfois difficile à écrire au sein d'une page Razor.

Nous définissons le modèle suivant ainsi que la vue écrite avec des HTML Helpers :

User.cs

public class User
{
[Display(Name = "Last name :")]
[Required(ErrorMessage = "Last name is required")]
public string LastName { get; set; }
[Display(Name = "First name :")]
[Required(ErrorMessage = "First name is required")]
public string FirstName { get; set; }
}

Index.cshtml

@using (Html.BeginForm("Index", "User", FormMethod.Post, new { @class = "form-horizontal" }))
{
<div class="form-group">
@Html.LabelFor(m => m.LastName, new { @class = "col-md-6" })
<div class="col-md-6">
@Html.TextBoxFor(m => m.LastName, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.LastName, null, new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.FirstName, new { @class = "col-md-6" })
<div class="col-md-6">
@Html.TextBoxFor(m => m.FirstName, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.FirstName, null, new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-6">
<input type="submit" value="OK" class="btn btn-info" />
</div>
</div>
}

Le code n'est pas lisible, car il mélange le C# et le HTML et son écriture n'est pas simple. En effet, pour rajouter une classe CSS à un HTML Helper, je dois d'abord trouver la bonne surcharge du HTML Helper et ensuite je suis obligé d'instancier un objet anonyme avec une propriété @class, car le mot class est un mot clef en C#. Tout ceci est propice aux erreurs et donc à une perte de temps au niveau du développement.

Voici ce que donne le code réécrit avec des Tags Helpers :

Index.html

<form asp-controller="Index" asp-action="User" method="post" class="form-horizontal">
<div class="form-group">
<label asp-for="LastName" class="col-md-6"></label>
<div class="col-md-6">
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="FirstName" class="col-md-6"></label>
<div class="col-md-6">
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<input type="submit" value="OK" class="btn btn-info" />
</div>
</form>

Les Tags Helpers sont les attributs asp-*. La vue est claire et simple à écrire et elle ne contient pas de code C#.

XI - Remerciements

Je tiens à remercier Hinault Romaric pour ses conseils de rédaction, ainsi que f-leb pour sa relecture.