J'ai déployé des apps chez Clever Cloud

J'ai découvert il y a peu le service d'hébergement cloud français Clever Cloud, quelques jours après avoir lu l'article de Guillaume Champeau sur la souveraineté numérique.

J'ai eu l'occasion de déployer des projets professionnels sur Microsoft Azure, Amazon AWS, ainsi que sur du Windows Server. Mon blog était hébergé sur Azure car je voulais un service managé : je ne veux pas perdre de temps à gérer le serveur ; et ça limite les options.

Donc en voyant un service français qui promettait un déploiement facile de mes applis .NET, je me suis empressé de tester.

Voici mes quelques petits retours, à l'usage des développeurs .NET

Le service

Le principe est simple : Clever Cloud nous fournit des machines virtuelles pour héberger nos applications. On pousse notre code, on configure nos nom de domaines, le type de VM et le nombre d'instances, et on les laisse gérer le reste.
La montée en charge est aussi gérée automatiquement, en définissant le nombre minimal est maximal d'instances, et la taille minimale et maximale de chaque VM.

Tout ça tourne sous Linux, comme vous vous en doutez sûrement, pas question donc de faire tourner des applications sous le vieux .NET Framework. Ici, uniquement du .NET Core ou .NET 5.

Ensuite, tout se configure à l'aide de variable d'environnement, autant les options de déploiement que les paramètres de notre application.

Déploiement d'une application

Le déploiement se fait directement depuis le dépôt Git de l'application. Pas question ici de pousser vos dll depuis Azure Devops Pipelines, c'est Clever Cloud qui fera le dotnet publish directement.

C'est assez déroutant pour quelqu'un comme moi qui aime bien customiser son déploiement, puisqu'il faut uniquement définir le dossier de l'application (avec la variable d'environnement' APP_FOLDER) et espérer que le dotnet publish fonctionnera bien.

Il est néanmoins possible de se brancher sur différents Hooks, pour exécuter des tâches avant ou après la compilation ; donc en théorie rien n'empêche de créer ses propres scripts pour faire tout ce dont on a besoin.

HTTPS

J'ai été surpris de ne pas trouver d'option pour activer HTTPS, mais en fait elle n'est pas nécessaire puisque tous les domaines ajoutés sont automatiquement protégés par Let's Encrypt.
Configurez le domaine dans la console Clever Cloud, faites pointer vos DNS, et attendez quelques minutes : c'est tout bon.

Il est aussi possible d'utiliser vos propres certificats si vous préférez, il faudra passer par l'API pour l'installer.

Les Addons

Étant donné qu'une application se limite rarement à du code qui s'exécute dans un container, nous avons ici ce qu'ils appellent des Addons : différent services managés à brancher à notre application : stockage de fichiers, bases de données, Redis ou Elastic Search.

Une fois lié à notre application, les informations de connexion sont automatiquement ajoutées en tant que variable d'environnement, pour pouvoir s'y connecter directement.

Stockage

Pour le stockage de fichier, le service proposé se nomme Cellar, et est compatible avec l'api de AWS S3. Il suffit donc d'utiliser le package Nuget AWSSDK.S3, pour s'y connecter de la manière suivante :

    private AmazonS3Client GetS3Client()
    {
        var host = "https://" + configuration["CELLAR_ADDON_HOST"];
        var s3config = new AmazonS3Config
        {
            ServiceURL = host
        };

        var accesskey = configuration["CELLAR_ADDON_KEY_ID"];
        var secretkey = configuration["CELLAR_ADDON_KEY_SECRET"];
        var credentials = new BasicAWSCredentials(accesskey, secretkey);

        var client = new AmazonS3Client(credentials, s3config);
        return client;
    }

Il est aussi possible de monter un dossier cloud en tant que dossier dans notre VM avec l'add-on File System Bucket. Mais à moins de vouloir déployer une application qui utilise déjà le file system, il est recommandé de préférer Cellar : moins cher, et mieux adapté à la montée en charge.

Bases de données

Concernant l'utilisation des bases de données, rien de compliqué : on ajoute l'addon, et on s'y connecte depuis notre application avec les identifiants générés.

Pour une base relationnelle, on a le choix entre MySQL et PostgreSQL. Pas de MS SQL Server ici, et ça sera certainement le plus gros frein à la migration d'applications .NET existantes, étant donné la popularité du serveur dans le monde du développement Microsoft.

Entity Framework Core est cependant parfaitement compatible avec ces deux SGBD, rien de bloquant donc si on démarre de nouveaux développements.

Nous avons aussi d'autres addons tels que MongoDB pour les amateurs de noSQL, ou Redis, qui sera vite indispensable si vous souhaitez par exemple utiliser SignalR dans vos applications cloud.

Conclusion

Après quelques jours d'utilisation sur mes projets personnels, j'adore le service. C'est moins poussé que d'autres services de cloud beaucoup plus connus, mais l'essentiel est là, et je pense qu'il ne manque pas grand chose pour déployer dessus de gros projets.
Si la migration de projets existant sera probablement compliquée dans de nombreux cas, les services de base et la possibilité de déployer des images Docker devrait permettre de faire à peu près tout ce dont on a besoin si on démarre un projet avec tout ça en tête.

Afficher une carte grâce aux données OpenStreetMap

Cette semaine est sorti le jeu Flight Simulator 2020, et certains joueurs se sont amusés de la présence d'un immense monolithe au nord de Melbourne.
Ce bug a pour origine une erreur dans le service cartographique OpenStreetMap (qui a entre-temps été corrigé), et cette anecdote m'a rappelé que ça faisait un moment que je voulais vous en parler.

OpenStreetMap, pour ceux qui ne connaissent pas, est ce qu'on pourrait appeler le « Wikipedia de la cartographie ». Il s'agit d'un service de cartographie libre, utlisable gratuitement, et pouvant être mis à jour par n'importe qui.
Vous pouvez donc tout à fait corriger la carte si vous voyez des erreurs dans votre voisinage, ou la compléter avec les noms de vos magasins préférés.

Je tiens un blog de développement web, donc je ne vais pas approfondir le sujet de la cartographie, bien que le sujet soit passionnant, mais nous allons plutôt voir comment nous pouvons utiliser les données d'OpenStreetMap pour afficher une carte sur nos applications web, à la place de Google Maps qui est un peu trop souvent utilisé sans se poser la question des alternatives.

Licence

Avant de commencer, on va voir rapidement la partie légale.

Les données brutes sont distribuées sous la licence libre Open Data Commons Open Database License (ODbL). Comme indiqué sur la page copyright d'OpenStreetMap, vous pouvez utiliser les données dans vos applications, à condition de créditer les contributeurs d'OpenStreetMap, et de redistribuer les données sous la même licence.
Lorsqu'on veut afficher la carte, on va utiliser les tuiles (les images des cartes, générées à partir des données brutes). Ces images sont sous licence Creative Commons CC-BY-SA 2.0 - les conditions d'utilisation sont globalement les mêmes que pour les données brutes : n'oubliez pas de mettre le copyright.

Affichage d'une carte avec Leaflet

Pour afficher une carte dynamique dans une page web, il va tout d'abord falloir choisir un serveur de tuile, le serveur qui nous fournira les fonds de carte.
La fondation OpenStreetMap n'a pas les moyens de financer des serveurs de tuiles utilisables librement par tout le monde, mais les données étant libres, de nombreux services proposent d'utiliser leurs serveurs - certains gratuitement pour des usages modérés, d'autres fournissant des services professionnels contre rémunération. Notez que vous pouvez aussi installer votre propre serveur de tuiles, si vous souhaitez être autonome.

Mon choix pour cet article s'est porté sur celui de l'association OpenStreetMap France.

On va maintenant utiliser une librairie JavaScript pour l'affichage. De nombreuses solutions existent, les plus utilisées sont OpenLayers et Leaflet ; j'utilise ce dernier dans cet exemple.

Pour afficher la carte, créer un div qui la contiendra, en lui précisant obligatoirement une taille.
Ensuite, vous pouvez initlialiser la carte, avec cet exemple de code :

<div id="mapDiv" style="width:800px; height: 400px;"></div>

<script>
    // Initialisation de la carte
    var map = L.map('mapDiv'); 

    // Initialisation des tuiles 
    var tileUrl = "https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png";
    var copyright = '&copy; Contributeurs de <a href="https://www.openstreetmap.org/">OpenStreetMap</a>';
    L.tileLayer(tileUrl, {
        attribution: copyright,
        maxZoom: 18,
        tileSize: 512,
        zoomOffset: -1,
    }).addTo(map);
    
    // On zoome sur Toulouse
    map.setView([43.60, 1.44], 10); 
</script>

Ce qui nous donne le résultat suivant :


Vous pouvez ensuite afficher toutes sorte d'informations sur la carte ou intéragir avec l'utilisateur ; je vous laisse jete un œil à la documentation pour ça.

Affichage vectoriel avec OpenMapTiles

Les données brutes étant libres, il est possible d'afficher les cartes autrement qu'en chargeant des images d'un serveur de tuiles. C'est ce que fait le projet OpenMapTiles, en proposant un système de tuiles vectorielles.
Au lieu d'afficher des images, c'est le navigateur qui dessine les objets de la carte ; permettant ainsi un affichage plus fluide et des plus d'interactions possibles (essayer de bouger avec le clic droit dans l'exemple plus bas). C'est le choix qu'a fait Qwant pour son service de cartographie Qwant Maps.

Ceci a malheureusement un coût, puisque le navigateur de l'utilisateur est plus fortement mis à contribution, et peut poser des problèmes de performances sur des ordinateurs peu puissants ou certains mobiles.

Pour afficher une carte vectorielle, j'utilise cette fois les serveurs de MapTiler, et la librairie Mapbox GL JS. OpenLayers et Leaflet ne gèrent pas l'affichage vectoriel nativement, mais peuvent fonctionner avec des plugins.

Pour afficher la carte, l'API est un peu différente, mais le système reste le même, je vous laisse regarder le code :

<div id="mapDiv" style="width:800px; height: 400px;"></div>

<script>
    var map = new mapboxgl.Map({
        container: 'mapDiv',
        style: 'https://api.maptiler.com/maps/bright/style.json?key=apikey',
        center: [1.44, 43.60],
        zoom: 10
    });
</script>

Vous pouvez ainsi voir le résultat - et voir la différence avec la carte précédente :

Vous noterez que même si l'affichage est différent, les cartes sont les mêmes. Les rues, les bâtiments ne changent pas de l'un à l'autre, car la source des données est la même.

J'espère que je vous ai donné envie d'essayer ça, et que la prochaine fois que vous devrez afficher une carte dans l'un de vos projets vous étudierez l'option OpenStreetMap.

Utiliser un composant Blazor dans une application ASP.NET Core MVC

Blazor est un nouveau framework qui arrive avec ASP.NET Core 3, et qui permets de faire du développement web client en C# au lieu de Javascript. Il propose 2 modèles d'hébergement : un mode client, où les dlls sont envoyées au client avec un runtime .NET, et exécutées grâce à WebAssembly ; et un mode serveur, où le code est exécuté par le serveur, et le client reçoit les mises à jour par une connexion SignalR. Pour plus d'informations, je vous invite à aller lire la documentation de Blazor.

Lorsque vous démarrez un nouveau projet ASP.NET Core3, vous avez la possibilité de créer un projet pur Blazor, ou un projet MVC, mais il est tout à fait possible d'ajouter des composants Blazor à une ou plusieurs page d'un projet MVC.

Note : cet article utilise Blazor en mode server-side, et est basé sur la preview 7 - il peut y avoir des changements dans les prochaines versions.

Migration vers ASP.NET Core 3

Blazor est une nouveauté d'ASP.NET Core 3. Si vous démarrez un nouveau projet ASP.NET Core 3, vous pouvez passer au paragraphe suivant.

Si vous souhaitez utiliser Blazor sur un projet existant sur un framework précédent, il vous faudra d'abord le migrer vers la dernière version : comme d'habitude, lisez la documentation pour effectuer la migration, notamment le passage sur la migration du code de routage pour utiliser .UseEndpoints().

Configuration de Blazor dans le Startup.cs

Pour activer Blazor dans le projet, il y a 2 modifications à faire dans le Startup.cs :

  • Dans la méthode ConfigureServices, ajoutez services.AddServerSideBlazor(); : enregistrement du service Blazor
  • Dans la méthode Configure, allez dans le UseEndoints et ajoutez endpoints.MapBlazorHub(); : configuration du hub SignalR utilisé par Blazor

On se retrouve avec le code qui ressemble à ça :

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddServerSideBlazor();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseStaticFiles();

    app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
        endpoints.MapBlazorHub();
    });
}

Ajout d'un composant Blazor dans une page

Pour mon exemple, j'ai repris le composant Counter du template Blazor, sans l'attribut @page

<h1>Counter</h1>

<p>Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    int currentCount = 0;

    void IncrementCount()
    {
        currentCount++;
    }
}

Puis on peux référencer le composant depuis notre vue MVC :

@(await Html.RenderComponentAsync<BlazorInMvc.Components.Counter>(RenderMode.Server))

Vous pouvez lancer la page : vous verrez le composant s'afficher avec le "Current count" à 0, et le bouton. Mais si vous cliquez dessus, rien ne se passe. Il faut ajouter un fichier javascript pour établir la connexion SignalR :

@section Scripts{
    <script src="~/_framework/blazor.server.js"></script>
}

Si l'url de votre page n'est pas à la racine du site, la connexion ne pourra pas s'effectuer correctement. Dans ce cas, il faut rajouter un élément dans l'en-tête de votre page pour préciser le dossier où elle se trouve. Si l'url de ma page est /Home/Counter, le dossier est /Home. J'ajoute donc à ma page :

@section Header{
    <base href="~/Home" />
}

Maintenant vous pouvez retester, et le composant fonctionne : les clics sur le bouton mettent bien à jour le compteur.

Utilisation de la navigation Blazor

À priori, l'utilisation de la navigation Blazor avec le routing MVC risque de poser soucis, mais en étant rigoureux dans la définition de ses routes, on peut faire fonctionner les 2 ensembles.

J'ai 2 pages MVC : /Home et /Home/Counter (même si l'une contient un composant Blazor, c'est toujours une page MVC), et je veux créer 2 pages Blazor : /Nav/Page1 et /Nav/Page2, qui fonctionneront en mode SPA lorsqu'on navigue de l'une à l'autre (et seulement entre ces 2).

Pour faire ça, on va créer un contrôleur qui recevra les requêtes vers les pages Blazor, et on configure le routing pour que toutes les pages sous l'url /Nav pointent vers la même Action.

NavController.cs :

public class NavController : Controller
{
    public IActionResult Index()
    {
        return View();
    }
}

Startup.cs :

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "blazor",
        pattern: "Nav/{page?}/{id?}",
        defaults: new { controller = "Nav", action = "Index" }
    );

    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");

    endpoints.MapBlazorHub();
});

L'action Index va contenir un composant Blazor, qui servira de routeur pour les 2 pages :

Nav.razor :

<Router AppAssembly="typeof(Startup).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="routeData" />
    </Found>
    <NotFound>
        <h1>Page not found</h1>
    </NotFound>
</Router>

Puis les composants Blazor correspondant à nos pages :

@page "/Nav/Page1"
@using Microsoft.AspNetCore.Components.Routing

<h3>Page1</h3>

<NavLink href="/Nav/Page2">Aller à la page 2</NavLink>

et

@page "/Nav/Page2"
@using Microsoft.AspNetCore.Components.Routing

<h3>Page2</h3>

<NavLink href="/Nav/Page1">Aller à la page 1</NavLink>

Les composants commencent par l'attribut @page, qui permets de définir à quelle url le composant doit répondre.

Maintenant vous pouvez passer de la page1 à la page2 sans avoir un rechargement complet de la page, la navigation se fait par la connexion SignalR. Si vous naviguer vers ou depuis une autre page, la navigation est une navigation html classique.

Code source

Vous pouvez récupérer le code source de cet article sur ma page Github : https://github.com/glacasa/Blog-BlazorInMvc.

Pour le compiler, vous devez avoir la dernière version de Visual Studio 2019 preview, ainsi que le framework dotnet core 3 preview 9 minimum.