Advertise with Googlier.com La casa del .NET http://blog.lacasa.fr/feed.xml Developpement web et .net ! ASP.NET Core, Blazor, HTML et Javascript en Mise à l'échelle d'une application ASP.NET Core SignalR http://blog.lacasa.fr/2023-02/mise-a-l-echelle-aspnet-core-signalr <p>ASP.NET Core SignalR, ou juste &quot;SignalR&quot; pour les intimes, est une bibliothèque pour ASP.NET Core qui va permettre d'ajouter des fonctionnalités temps-réel à vos applications.<br /> Vous pourrez par exemple créer envoyer des notifications en temps réel à vos utilisateurs, ou créer des jeux multijoueurs.</p> <p>Le but de cet article n'est pas de parler de la technologie elle-même, mais de la façon de gérer sa mise à l'échelle.<br /> Si vous ne connaissez pas déjà SignalR, je vous invite à aller la découvrir <a href="https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-7.0">dans sa documentation officielle</a>.</p> <h2>Mise à l'échelle</h2> <p>Lorsqu'on utilise SignalR sur un seul serveur, tout fonctionne très bien puisque notre serveur garde en mémoire la liste des clients connectés, et va pouvoir envoyer des messages à chacun d'entre eux.<br /> Mais si on veut gérer beaucoup de clients connectés, on risque d'arriver aux limites de notre serveur (en mémoire utilisée, ou en nombre de connexions ouvertes simultanées).</p> <p>Si on peut bien sûr passer sur un serveur plus puissant, on envisagera aussi la possibilité de lancer plusieurs serveurs derrière un load-balancer. C'est bien sûr possible, mais il va nous falloir un peu de configuration pour que ça fonctionne.</p> <h3>Connexion du client au serveur</h3> <p>Si on déploie notre serveur SignalR sur un 2ème serveur avec un load-balancer, on va directement avoir des soucis, puisque les clients auront des erreurs à l'ouverture de la connexion.</p> <p>Ceci est du au fait que SignalR va pouvoir gérer plusieurs transports : un premier appel est faite pour négocier le meilleur transport (<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API">WebSocket</a> si le client le supporte, sinon Server-Sent Events ou Long-Polling), puis un second qui ouvre la vraie connexion.<br /> Entre ces deux appels, les informations restent en mémoire sur le serveur, et donc si le client ne se connecte pas à la même instance, la connexion échoue.</p> <p>Pour remédier à ce problème, nous avons 3 possibilités :</p> <p><strong>Affinité de session</strong><br /> Ceci est à configurer au niveau du load-balacer. Chaque client sera lié à l'un de vos serveurs, et ses appels arriveront toujours sur le même.</p> <p><strong>Forcer l'usage de websocket</strong><br /> Si vous êtes sûr que les clients <a href="https://caniuse.com/mdn-api_websocket">supportent WebSocket</a>, alors vous pouvez oublier l'étape de négociation, et forcer la connexion en SignalR.<br /> Ainsi, plus besoin d'affinité de session, vu qu'il n'y a qu'une requête - elle fonctionnera quel que soit le serveur qui la traitera.</p> <p>En Javascript, on modifiera la connexion de la manière suivante :</p> <pre><code>let connection = new signalR.HubConnectionBuilder() .withUrl(&quot;/appHub&quot;, { skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets }) .build(); </code></pre> <p><strong>Utiliser Azure SignalR Service</strong><br /> Si vous hébergez votre application dans le Cloud Azure, vous pouvez utiliser le service dédié Azure SignalR Service. Ce service sert de proxy entre les clients et les serveurs, et va gérer ces problématiques de connexion pour vous.</p> <pre class="mermaid"> graph BT subgraph Serveurs web1[Web] web2[Web] azsignalr(Azure SignalR) azsignalr<-->web1 azsignalr<-->web2 end clientA(Client A) clientB(Client B) clientC(Client C) clientA<--ws-->azsignalr clientB<--ws-->azsignalr clientC<--ws-->azsignalr </pre> <p>Pour configurer SignalR avec Redis, il faut référencer le package Nuget <code>Microsoft.Azure.SignalR</code>, et appeler la méthode suivante lors de la configuation de SignalR :</p> <pre><code>services.AddSignalR().AddAzureSignalR(azureSignalrConnectionString); </code></pre> <h2>Communication entre clients sur instances différentes</h2> <p>Maintenant qu'on a résolu notre problème de connexion, il reste un problème à régler.<br /> Les clients sont bien connectés, mais chacun de nos serveurs n'a connaissance que d'une partie des clients, il n'est pas possible d'envoyer des messages à un client connecté sur un autre serveur.</p> <p>Si on veut envoyer un message à tous les clients connectés, il va nous falloir trouver une solution. La documentation d'ASP.NET nous en propose deux.</p> <h3>Avec Azure SignalR Service</h3> <p>Si vous avez déjà choisi cette option dans la première partie, rien de plus à faire :<br /> tous les clients sont bien connectés au même service Azure, tout est géré de manière transparente pour vous.</p> <h3>Avec Redis</h3> <p>L'autre solution est d'utiliser un backplane Redis. Cette fois, on n'a pas un proxy qui gère les connexions, mais Redis et ses fonctionnalités PUB/SUB permet aux serveurs de communiquer entre eux.</p> <pre class="mermaid"> graph BT subgraph Serveurs web1[Web] web2[Web] redis[Redis] web1<-->redis web2<-->redis end clientA(Client A) clientB(Client B) clientC(Client C) clientA<--ws-->web1 clientB<--ws-->web2 clientC<--ws-->web2 </pre> <p>Si l'un de vos serveurs veut envoyer un message, ça se passe en 3 temps :</p> <ul> <li>Il envoie le message au serveur Redis, avec le message et les clients visés</li> <li>Tous les serveurs (dont l'émetteur du message) reçoivent une notification du serveur Redis</li> <li>Chacun des serveurs envoie le message aux clients qu'il connait</li> </ul> <p>Pour configurer SignalR avec Redis, il faut référencer le package Nuget <code>Microsoft.AspNetCore.SignalR.StackExchangeRedis</code>, et appeler la méthode suivante lors de la configuation de SignalR :</p> <pre><code>services.AddSignalR().AddStackExchangeRedis(redisConnectionString); </code></pre> <h2>Multi application</h2> <p>Si vous avez plusieurs applications SignalR, il est possible de les configurer pour qu'elles utilisent le même serveur Redis, mais avec des canaux différents pour éviter qu'elles ne se marchent dessus :</p> <pre><code>services.AddSignalR() .AddStackExchangeRedis(connectionString, options =&gt; { options.Configuration.ChannelPrefix = &quot;App1&quot;; }); </code></pre> <pre class="mermaid"> graph BT redis(Redis) subgraph Application web web1[Web] web2[Web] end subgraph API mobile api1[API] api2[API] end web1<--channel_web-->redis web2<--channel_web-->redis api1<--channel_api-->redis api2<--channel_api-->redis navigateur1<--ws-->web1 navigateur2<--ws-->web2 navigateur3<--ws-->web2 mobile1<--ws-->api1 mobile2<--ws-->api2 mobile3<--ws-->api2 </pre> <p>Mais ce à quoi on ne pense pas forcément, c'est qu'on peut vouloir que ces deux applications utilisent les mêmes canaux.<br /> Imaginons que dans notre exemple, on veuille qu'une action d'un utilisateur mobile envoie des notifications vers des utilisateurs du site web, en mettant le même <code>ChannelPrefix</code> (ou en laissant la valeur vide), les deux applications pourront communiquer sans problème</p> <h2>Le cas particulier de Blazor Server</h2> <p>Blazor permet de développer des applications web dynamiques, en utilisant uniquement C# et razor.<br /> Il existe deux modes de déploiement : Webassembly, où le code est exécuté dans le navigateur, et Blazor Server, où le code est exécuté sur le serveur, et la communication avec le client se fait via une connexion SignalR.</p> <p>Et donc si on utilise Blazor Server, on risque de se retrouver avec les mêmes problèmes pour gérer la mise à l'échelle de la connexion SignalR. Mais Blazor arrive avec ses spécificités dont nous allons devoir tenir compte :</p> <p>Concernant le premier problème de connexion, Blazor va lui aussi nécessiter un premier appel, avant d'initialiser la connexion SignalR. Ce premier appel initialise l'état du composant, et on ne pourra pas y couper, cette fois l'affinité de session est obligatoire dans tous les cas.</p> <p>Si vous utilisez Azure SignalR Service, il faudra aussi configurer cette affinité auprès entre le serveur et le service :</p> <pre><code>builder.Services.AddServerSideBlazor().AddAzureSignalR(options =&gt; { options.ServerStickyMode = Microsoft.Azure.SignalR.ServerStickyMode.Required; }); </code></pre> <p>Le deuxième problème ici n'en est finalement pas un : Blazor utilise la connexion SignalR pour avoir échanger entre le client et le serveur, mais les clients sur différentes instances n'ont pas forcément besoin d'échanger des messages. Dans ce cas, le backplane Redis n'est pas nécessaire.</p> <p>Si vous souhaitez plus d'informations sur le déploiement d'une application Blazor Serveur, je vous renvoie vers <a href="https://learn.microsoft.com/en-us/aspnet/core/blazor/host-and-deploy/server">la documentation dédiée</a>.</p> http://blog.lacasa.fr/2023-02/mise-a-l-echelle-aspnet-core-signalr Fri, 17 Feb 2023 18:00:00 GMT Calculer un itinéraire grâce à Itinero et aux données OpenStreetMap http://blog.lacasa.fr/2022-11/calculer-itineraire-itinero-openstreetmap <p>Maintenant que nous savons afficher des cartes, on va essayer d'aller un peu plus loin ; aujourd'hui, nous allons calculer et afficher un itinéraire entre 2 points.</p> <p>Le serveur de carte que nous avons installé précédemment ne va pas pouvoir nous aider, nous allons devoir utiliser un autre service pour ça. <a href="https://wiki.openstreetmap.org/wiki/FR:Calcul_d%27itin%C3%A9raires">Il en existe plusieurs</a>, j'ai décidé d'utiliser <a href="https://www.itinero.tech/">Itinero</a>, qui est une librairie .NET, et qui propose aussi une api déployable directement.</p> <p>Vous pouvez lire <a href="http://docs.itinero.tech/docs/itinero/index.html">la documentation complète d'Itinero</a> pour entrer plus en détail dans le sujet.</p> <h2>Création du fichier RouteDb</h2> <p>Nous avons déjà du effectuer une conversion des données OpenStreetMap vers un format vectoriel, le principe ici est le même, puisqu'Itinero ne va pas travailler non plus sur les données brutes, mais sur des données ne contenant que le nécessaires pour calculer un itinéraire (uniquement les routes, inutiles de s'encombrer des batiments ou des cours d'eau).</p> <p>Les données OpenStreetMap peuvent toujours être récupérées par région depuis le site <a href="http://download.geofabrik.de/">Geofabrik</a>, et ensuite <a href="https://github.com/itinero/idp">Itinero Data Processor</a> permets d'effectuer la conversion en ligne de commande.</p> <p>Tout ce que permets IDP est faisable par code grâce à un packge Nuget, et c'est ce que j'ai choisi de faire, afin d'automatiser complètement la récupération des données OSM, la conversion, et l'envoi du résultat vers un stockage cloud.</p> <p>Pour cela, j'ai créé une application console, dans laquelle j'ai référencé les packages Nuget <code>Itinero</code> et <code>Itinero.IO.Osm</code>.<br /> Puis quelques lignes de code pour récupérer les données de Midi-Pyrénées, les convertir, et sauvegarder le résultat.</p> <pre><code class="language-csharp">var routerDb = new RouterDb(); var client = new HttpClient(); var url = &quot;http://download.geofabrik.de/europe/france/midi-pyrenees-latest.osm.pbf&quot;; using (var response = await client.GetAsync(url)) { routerDb.LoadOsmData(response.Content.ReadAsStream(), Vehicle.Car, Vehicle.Bicycle); } using (var stream = new FileInfo(@&quot;midi-pyrenees.routerdb&quot;).Open(FileMode.Create)) { routerDb.Serialize(stream); } </code></pre> <p>Deux choses importantes à voir dans ce code :</p> <ul> <li><code>routerDb.LoadOsmData</code> : cette méthode lis le fichier OpenStreetMap, et récupère les informations nécessaires au calcul d'itinéraire.<br /> Une liste de <code>Vehicle</code> est passé en paramètre, car selon le type d'itinéraire qu'on veut calculer, les données à utiliser ne seront pas les mêmes.</li> <li><code>routerDb.Serialize</code> : cette méthode permets d'enregistrer nos données dans un fichier RouterDb, ensuite nous n'utiliserons que ce fichier pour tous nos calculs d'itinéraires.</li> </ul> <p>On a une liste de profils de véhicules prédéfinis (voiture, vélo, piéton ou camion…), et <a href="http://docs.itinero.tech/docs/itinero/basic-concepts/profiles/lua.html">on peut définir d'autres profils personnalisés</a>.</p> <p>Vous pouvez lancer cette application, et la laisser tourner quelques minutes, ça sera plus ou moins long selon la taille de la zone à convertir, et le nombre de profils de véhicules que vous souhaitez gérer.</p> <h2>Calcul d'un itinéraire</h2> <p>Maintenant qu'on a un RouterDb, on va pouvoir l'utiliser pour calculer nos itinéraires.</p> <p>On commence par charger les données depuis le fichier, puis on crée un <code>Router</code> utilisant ces données</p> <pre><code class="language-csharp">RouterDb routerDb; await using (Stream routerDbFile = await container.GetFile(&quot;midi-pyrenees.routerdb&quot;)) { routerDb = RouterDb.Deserialize(routerDbFile); } var router = new Router(routerDb); </code></pre> <p>Il nous faut ensuite créer un <a href="http://docs.itinero.tech/docs/itinero/basic-concepts/profiles/index.html">Profile</a> : avec quel type de véhicule on souhaite calculer l'itinéraire, et si on veut le plus rapide ou le plus court.</p> <p>Là aussi, je reste sur les profils prédéfinis :</p> <pre><code class="language-csharp">var profile = Vehicle.Car.Fastest(); // Trajet le plus rapide en voiture var profile2 = Vehicle.SmallTruck.Shortest(); // Trajet le plus court en camionnette </code></pre> <p>Puis on choisit le point de départ et d'arrivée avec leurs coordonnées GPS, et on peut calculer l'itinéraire</p> <pre><code class="language-csharp">var start = router.Resolve(profile, 43.60442f, 1.44403f); var end = router.Resolve(profile, 43.23370f, 1.57595f); var route = router.Calculate(profile, start, end); </code></pre> <p>L'objet <a href="http://docs.itinero.tech/docs/itinero/basic-concepts/route.html">Route</a> contient toutes les informations qui nous intéressent, notamment :</p> <ul> <li><code>Shape</code> et <code>ShapeMeta</code> : les coordonnées de tous les points représentant la route, et les métadonnées associées</li> <li><code>TotalDistance</code> et <code>TotalTime</code> : la distance totale et la durée estimée du trajet</li> </ul> <p>Ces informations suffisent maintenant à connaitre toutes les informations du trajet. Pour le dessiner sur une carte, on peut par exemple dessiner un trait entre chaque point du tableau <code>Shape</code>.</p> <p>Cependant, plutôt que de faire ça manuellement, on préfèrera certainement utiliser le format GeoJson pour envoyer les données à une librairie compatible. Il suffit d'appeler la méthode <code>ToGeoJson()</code></p> <pre><code class="language-csharp">// Envoi du résultat depuis une API ASP.NET return Ok(route.ToGeoJson()); </code></pre> <p>Il suffit ensuite de récupérer les données en JavaScript et de les passer à la librairie <a href="https://maplibre.org">MapLibre</a> pour pouvoir l'afficher :</p> <pre><code class="language-js">// On fait un appel à l'API pour recevoir les informations de l'itinéraire const response = await fetch('/Itineraire'); const data = await response.json(); const map = initMap(); // voir les articles précédents pour l'initialisation de la carte // On crée une source de données avec les informations geojson reçues map.addSource('route', { type: 'geojson', data: data }); // On affiche les données de la source avec l'id &quot;route&quot; map.addLayer({ 'id': 'route', 'type': 'line', 'source': 'route', 'layout': { 'line-join': 'round', 'line-cap': 'round' }, 'paint': { 'line-color': '#888', 'line-width': 8 } }); </code></pre> <h2>Démonstration</h2> <p>Voilà donc le résultat : j'ai repris l'affichage de la carte existant, et on peut afficher par dessus le trajet entre Toulouse et Saverdun.</p> <div id="itineromap" class="maps"></div> <script> const map = initOpenMapTiles("itineromap", "https://tiles.openfreemap.org/styles/liberty"); async function chercherItineraire(){ const searchBtn = document.getElementById("search-btn"); searchBtn.setAttribute("disabled","disabled"); const k = document.getElementById("type-itineraire").value; await loadItineroData(map, k); searchBtn.removeAttribute("disabled"); } </script> <select id="type-itineraire"> <option value="fastest">Le plus rapide</option> <option value="shortest">Le plus court</option> </select> <button id="search-btn" onclick="chercherItineraire()">Chercher</button> http://blog.lacasa.fr/2022-11/calculer-itineraire-itinero-openstreetmap Wed, 30 Nov 2022 15:00:00 GMT Partager ses clés OpenPGP et trouver celles de vos correspondants http://blog.lacasa.fr/2022-11/Partager-et-trouver-cles-openpgp <p><a href="https://www.openpgp.org/">OpenPGP</a> est un format de cryptographie utilisé pour le chiffrement des e-mails de bout en bout.<br /> Le chiffrement avec OpenPGP est géré nativement par <a href="https://www.thunderbird.net/">Thunderbird</a>, et fonctionne avec n'importe quelle adresse e-mail. Des services comme <a href="https://proton.me/">Proton</a> vous proposent un usage simplifié en embarquant directement son usage dans leur webmail.<br /> Vous pouvez trouver une liste de logiciels compatibles sur le site <a href="https://www.openpgp.org/software/">OpenPGP.org</a></p> <p>Le chiffrement repose sur une paire de clés (publique et privée) associée à chaque adresse e-mail : lorsque vous écrivez à quelqu'un, vous utilisez sa clé publique pour chiffrer le message, le destinataire utilisera sa clé privée pour le déchiffrer et lire son contenu.</p> <p>S'il est bien sûr possible de partager sa clé publique en la donnant en main propre à ses correspondants, ce n'est pas forcément le plus pratique ; la question se pose ainsi de savoir comment partager sa clé publique par internet de manière sécurisée.</p> <h2>Partager sur un serveur de clés</h2> <p>Historiquement, on partageait ses clés sur un réseau de serveurs de clés décentralisés. Cette approche a trouvé ses limites, et aujourd'hui plusieurs projets OpenPGP se sont rassemblés pour créer et utiliser un serveur de clés principal de confiance : <a href="https://keys.openpgp.org/about">https://keys.openpgp.org/</a>.</p> <p>Ceci ne vous empêche en rien d'utiliser d'autres serveurs, mais en envoyant vos clés sur celui-là, votre adresse e-mail sera vérifiée (empêchant quiconque de fournir une fausse clé pour votre adresse), et les utilisateurs qui auront ce serveur configuré trouveront votre clé plus facilement.</p> <p>Pour envoyer votre clé, allez sur la page <a href="https://keys.openpgp.org/manage">&quot;gérer votre clé&quot;</a> et entrez votre adresse e-mail. Vous allez recevoir un message avec un lien vous permettant d'envoyer vos clés ou de les supprimer du serveur.</p> <p>Lorsque la clé est sur un serveur, elle est trouvable par l'API :<br /> <a href="https://keys.openpgp.org/pks/lookup?op=get&amp;options=mr&amp;search=test@lacasa.fr">https://keys.openpgp.org/pks/lookup?op=get&amp;options=mr&amp;search=test@lacasa.fr</a></p> <p>Il est bien sûr toujours possible d'héberger son propre serveur de clés, et ça peut être pertinent si vous êtes en position d'autorité de confiance pour un grand nombre de clés. <a href="https://proton.me/blog/why-we-created-protonca">C'est par exemple ce qu'a fait Protonmail</a> pour distribuer les clés de tous ses utilisateurs.</p> <p>Cependant, il faut que les utilisateurs enregistrent manuellement votre serveur de clés dans leurs logiciels pour pouvoir en profiter, vous aurez probablement du mal à convaincre beaucoup de monde de le faire. C'est pour ça que nous avons un autre système de partage de clés : WKD</p> <h2>Partager directement avec Web Key Directory</h2> <p>Plutôt que de passer par un serveur externe en qui il faut avoir confiance, il est possible d'héberger ses propres clés.<br /> L'idée est simple : si une adresse e-mail est en '@lacasa.fr', on doit pouvoir faire confiance au site web <code>https://lacasa.fr</code> pour fournir sa clé publique.</p> <p>N'importe quel hébergement statique sera capable de fournir les clés, puisqu'il suffit de créer une arborescence et de donner le bon nom aux fichiers</p> <pre><code>https://[domaine]/.well-known/openpgpkey/hu/[Hashed-UserId]?l=[UserId] </code></pre> <p>Le nom du fichier est un hash du userId. Ainsi, pour l'adresse e-mail <code>test@lacasa.fr</code>, le hash de <code>test</code> est <code>iffe93qcsgp4c8ncbb378rxjo6cn9q6u</code></p> <p>La clé de cette adresse e-mail peut donc être publiée à cette adresse :<br /> <a href="https://lacasa.fr/.well-known/openpgpkey/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test">https://lacasa.fr/.well-known/openpgpkey/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test</a></p> <p>Certains serveurs WKD ont besoin d'avoir le userId passé en clair, on l'ajoute donc le paramètre si besoin : <code>l=test</code></p> <p>Il existe un mode avancé pour proposer ses clés, quand pour des raisons pratiques on ne peut pas ou on ne souhaite pas héberger ses clés directement sous le domaine racine.<br /> Ce mode avancé permets aussi de centraliser l'hébergement des clés de plusieurs domaines sur un seul serveur.</p> <p>Pour ça, il faut créer un sous-domaine <code>openpgpkey</code> au domaine de l'adresse e-mail, et ajouter aussi un dossier avec le nom du domaine dans le dossier <code>.well-known/openpgpkey</code></p> <pre><code>https://openpgpkey.[domaine]/.well-known/openpgpkey/[domaine]/hu/[Hashed-Userid]?l=[UserId] </code></pre> <p>La clé de mon adresse e-mail est donc aussi accessible à cette adresse :<br /> <a href="https://openpgpkey.lacasa.fr/.well-known/openpgpkey/lacasa.fr/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test">https://openpgpkey.lacasa.fr/.well-known/openpgpkey/lacasa.fr/hu/iffe93qcsgp4c8ncbb378rxjo6cn9q6u?l=test</a></p> <p>Ainsi on peut héberger les clés en dehors de son site web principal, et héberger plusieurs domaines sur le même serveur en faisant pointer les différents sous-domaine sur ce site web avec un CNAME.</p> <p>Le serveur de clés keys.openpgp.org propose ainsi <a href="https://keys.openpgp.org/about/usage#wkd-as-a-service">un service WKD</a> utilisable avec votre propre domaine :<br /> Créez le CNAME qui pointe vers <code>wkd.keys.openpgp.org</code>, et vos clés déjà hébergées sur leur serveur deviennent accessible en WKD sur votre propre domaine.</p> <h2>.NET et les clés OpenPGP</h2> <p>Si je me suis plongé dans le partage de clés OpenPGP, c'était aussi pour pouvoir chiffrer des e-mails envoyés depuis mes applications métier. J'ai donc fait une librairie .NET standard <a href="https://github.com/pericia/Pericia.OpenPgp">Pericia.OpenPgp</a> pour retrouver facilement des clés sur un serveur de clés ou par WKD, que vous pouvez ensuite utiliser avec <a href="https://www.bouncycastle.org/csharp/">BoucyCastle</a>.</p> <pre><code>var key1 = openPgpSearch.SearchWebKeyDirectory(mailAddress); var key2 = openPgpSearch.SearchHttpKeyServer(mailAddress); </code></pre> <p>Cette librairie propose aussi une extension à ASP.NET pour distribuer vos clés via WKD (aussi bien en mode direct qu'en mode avancé). Il vous suffit de mettre vos clés publiques dans un dossier, et elles seront distribuées automatiquement.</p> <pre><code>// Configure services var keyDirectory = builder.Environment.ContentRootFileProvider.GetDirectoryContents(&quot;Keys&quot;); builder.Services.AddWebKeyDirectory(keyDirectory); // Configure app app.UseWebKeyDirectory(); </code></pre> http://blog.lacasa.fr/2022-11/Partager-et-trouver-cles-openpgp Mon, 14 Nov 2022 12:00:00 GMT Générer ses propres tuiles vectorielles à partir des données OpenStreetMap http://blog.lacasa.fr/2022-04/generer-tuiles-vectorielles-openstreetmap <p>Dans l'article précédent je vous ai montré comment il était possible d'héberger son propre serveur de carte, et donc de ne plus être dépendant de services externes pour afficher des cartes sur un site web. À un détail près, puisque même si on peut être autonome avec le serveur, il fallait quand même passer par un service payant pour pouvoir récupérer et mettre à jour les données cartographiques. Nous allons voir maintenant comment faire ça nous même.</p> <h2>Pourquoi les données cartographiques sont-elles payantes ?</h2> <p>On peut légitimement se poser cette question : puisque les données cartographiques viennent d'OpenStreetMap, leur usage est censé être libre - dans ce cas, pourquoi le téléchargement des données pour le serveur de tuile est-il payant ?</p> <p>La réponse est toute simple : parce que les données OpenStreetMap ne peuvent pas être utilisées directement, et nécessitent donc une phase de conversion.</p> <p>Il est tout à fait possible d'héberger une copie du site OpenStreetMap, avec son serveur de tuiles «images», et donc d'utiliser directement les données existantes. J'ai déjà mentionné le site <a href="https://switch2osm.org/serving-tiles/">Switch2OSM</a> si le sujet vous intéresse.</p> <p>Mais le rendu de ces tuiles images se fait vieillot, et je préfère proposer un affichage vectoriel, qui n'a que des avantages, maintenant que c'est supporté correctement sur tous les appareils un peu récents : format plus léger, zooms et déplacements plus fluides.</p> <p>Nous allons donc voir comment effectuer la conversion des données natives OpenStreetMap vers un format vectoriel.</p> <h2>Le format OpenMapTiles</h2> <p>Les équipes de <a href="https://maptiler.fr/">MapTiler</a>, le service qui propose de télécharger les données cartographiques, sont à l'origine du <a href="https://openmaptiles.org/schema/">format de tuiles verctorielles OpenMapTiles</a>, distribué sous licence libre et gratuit à utiliser.<br /> C'est ce format que j'ai utilisé dans mes articles précédent lorsque je parlais de tuiles vectorielles.</p> <p>Étant donné que le format est libre, nous avons donc pu avoir différentes initiatives pour proposer des serveurs compatibles, ainsi que des projets pour convertir les données OpenStreetMap vers ce format. Nous pouvons citer en vrac (liste non exhaustive) : <a href="https://github.com/openmaptiles/openmaptiles-tools">Openmaptiles-tools</a>, <a href="https://github.com/kartotherian/kartotherian/blob/master/packages/tilerator/README.md">Tilerator</a>, <a href="https://github.com/systemed/tilemaker">Tilemaker</a>, ou <a href="https://github.com/onthegomap/planetiler">Planetiler</a></p> <p>C'est ce dernier projet que j'ai décidé d'utiliser et que je vais vous présenter</p> <h2>Génération d'un fichier mbtiles avec Planetiler</h2> <p>Après avoir essayé différents outils pour générer mes données cartographiques vectorielles, j'ai décidé d'utiliser Planetiler car il est le plus simple à utiliser : il suffit d'une ligne de commande pour obtenir un fichier de <code>mbtiles</code>, qu'il ne nous reste plus qu'à envoyer sur notre serveur.</p> <p>Tout d'abord, il faut savoir quelle zone on veut convertir. Vous pouvez vous contenter d'une région ou d'un pays, mais si vous souhaitez la planète entière il vous faudra beaucoup de mémoire RAM, et quelques heures à attendre.</p> <p>Les données par région sont fournies par le site <a href="https://download.geofabrik.de/">Geofabrik</a>, et seront téléchargées automatiquement par l'outil. De mon côté, je me contente de <a href="https://download.geofabrik.de/europe/france.html">la carte de France</a> (métropole + corse)</p> <p>Pour lancer la conversion, 2 options.</p> <p>Utilisation de l'outil Java :</p> <pre><code>wget https://github.com/onthegomap/planetiler/releases/latest/download/planetiler.jar java -Xmx32g -jar planetiler.jar --download --area=france </code></pre> <p>Avec l'image Docker:</p> <pre><code>docker run -e JAVA_TOOL_OPTIONS=&quot;-Xmx32g&quot; -v C:\data:/data ghcr.io/onthegomap/planetiler:latest --download --area=france </code></pre> <p>Ces lignes de commande viennent du <a href="https://github.com/onthegomap/planetiler/blob/main/README.md">REAMDE de l'outil</a>, que j'ai très légèrement adaptées :</p> <ul> <li>l'option Java <code>-Xmx32g</code> représente la mémoire utilisable par l'outil. Elle est limitée à 1Go dans leur exemple, suffisant pour la principauté de Monaco, mais limite pour la France</li> <li>et j'ai remplacé la variable shell $(pwd) par un chemin en dur, car je le fait tourner sous Windows et que la syntaxe Powershell n'est pas exactement la même (j'avoue que je n'ai pas cherché comment l'écrire plus proprement)</li> </ul> <p>Après trois quarts d'heure à mouliner sur mon PC, j'ai enfin obtenu ma carte, qu'on peut maintenant mettre sur un serveur de tuiles.</p> <p>Si vous souhaitez convertir la planète entière, il vous faudra idéalement plus de 100Go de RAM, et de nombreux cœurs dans votre processeur. Vous pouvez lire <a href="https://github.com/onthegomap/planetiler/blob/main/PLANET.md">une documentation spécifique</a> dans laquelle je ne rentrerai pas en détail.<br /> Personnellement, je n'ai même pas essayé de lancer ça sur mon PC, il faudra certainement préférer utiliser des machines virtuelles cloud pour ça. Pour exemple, la conversion complète de la planète a été effectuée en un peu plus de 3h sur une machine avec 16 cœurs et 128Go de ram.</p> <p>Avec ce 3ème article, vous avez maintenant tout le nécessaire pour héberger vos cartes et les mettre à jour régulièrement, et j'espère vous avoir convaincu de le faire !</p> http://blog.lacasa.fr/2022-04/generer-tuiles-vectorielles-openstreetmap Sat, 23 Apr 2022 12:00:00 GMT Héberger son serveur de cartes OpenStreetMap http://blog.lacasa.fr/2022-04/heberger-carte-openstreetmap <p>Dans <a href="/2020-08/afficher-carte-openstreetmap">un précédent article</a>, j'ai montré comment on pouvait afficher des cartes grâce aux données d'OpenStreetMap. Mais même si ça a l'avantage indéniable de permettre de se passer de Google, on doit toujours passer par un service externe.<br /> Nous allons voir maintenant comment nous pouvons héberger nous-même nos cartes, afin de supprimer cette dépendance.</p> <h2>Démarrage du serveur de cartes</h2> <p>Il existe plusieurs façon d'héberger nos cartes. Vous pouvez déployer un clone du site OpenStreetMap lui même, en suivant <a href="https://switch2osm.org/serving-tiles/">cet article de Switch2OSM</a>.</p> <p>De mon côté, je préfère avoir un rendu vectoriel de mes cartes, j'ai donc préféré utiliser un serveur <a href="https://github.com/maptiler/tileserver-gl">TileServer GL</a> - qui a de plus l'avantage de pouvoir être déployé très simplement, grâce à <a href="https://hub.docker.com/r/maptiler/tileserver-gl">une image Docker</a>.<br /> D'autres logiciels serveurs existent, <a href="https://wiki.openstreetmap.org/wiki/Tile_servers">une liste est disponible sur le wiki d'Openstreetmap</a></p> <p>Ouvrez un terminal Powershell, aller dans un dossier vide (dans lequel les données de cartographie seront stockées), et tapez la commande suivante :</p> <pre><code>docker run --rm -it -v ($pwd.Path+&quot;:/data&quot;) -p 8080:80 maptiler/tileserver-gl </code></pre> <p>Le serveur démarre, et vous pouvez y accéder directement sur <a href="http://localhost:8080">http://localhost:8080</a></p> <p>Pour le moment, nous n'avons pas récupérées les données cartographiques d'OpenStreetMap, le serveur a donc du télécharger des données d'exemple. Si vous jetez un œil dans le répertoire de travail, il devrait donc y avoir un fichier <code>zurich_switzerland.mbtiles</code>, qui vous permets de visualiser la carte de la ville de Zurich.</p> <h2>Déploiement dans Azure</h2> <p>Pour mettre ça en ligne, j'ai créé un App Service sur Azure, dans lequel <a href="/assets/azure-maps.png">j'ai déployé l'image Docker</a>.<br /> Vous pouvez bien sûr déployer ça où vous voulez, tant que vous avez accès à du Docker ou du NodeJS. Je vous laisse vous adapter, l'important est de pouvoir écrire dans le répertoire data.</p> <p>Pour que ça puisse démarrer, j'ai du modifier quelques options dans la configuration de l'App Service :</p> <ul> <li> <p>L'application démarre en interne sur le port 80, ce qui est bloqué pour raisons de sécurité. On va démarrer à la place sur le port 8080, en ajoutant en &quot;commande de démarage&quot; : <code>-p 8080</code>.<br /> Il faut aussi ajouter une variable d'environnement <code>WEBSITES_PORT</code>, avec la valeur 8080, pour qu'Azure sache où chercher notre site.</p> <ul> <li>UPDATE (29 novembre 2022) : dans la dernière version de tileserver-gl, le port utilisé est maintenant 8080 par défaut</li> </ul> </li> <li> <p>On va aussi avoir besoin d'un endroit pour stocker le fichier mbtiles. Commencez par créer un compte de stockage, et à l'intérieur ajouter un nouveau Partage de fichier.<br /> Revenez ensuite dans la Configuration de l'App Service, et dans Mappages de chemin d'accès, ajoutez le Partage de fichier que vous venez de créer, avec le chemin de montage <code>/data</code></p> </li> <li> <p>Et tant que vous êtes dans l'interface, pous pouvez aller dans la section CORS pour définir les domaines autorisés à utiliser votre serveur de carte</p> </li> </ul> <h2>Téléchargement des données</h2> <p>En effet, maintenant que votre serveur tourne, il faut ajouter les données des cartes. Le format utilisé ici, avec les fichiers mbtiles, n'est pas le format natif d'OpenStreetMap. Vous pouvez télécharger les données converties sur le site d'<a href="https://openmaptiles.com/downloads/planet/">OpenMapTiles</a>.</p> <p>Il s'agit d'un service payant qui fournit les données avec des mises à jour hebdomadaires, mais vous pouvez récupérer des données gratuitement pour des projets non-commerciaux/open-source, ou pour vos tests. Si vous ne souhaitez pas vous inscrire, vous pouvez télécharger directement <a href="https://github.com/glacasa/tileserver-gl-config/raw/main/2017-07-03_france_toulouse.mbtiles">la carte de Toulouse</a>.</p> <p>Mettez ce fichier <code>.mbtiles</code> dans le répertoire <code>/data</code>, et redémarrez : maintenant le serveur est prêt, et devrait afficher votre carte.</p> <h2>Sélection d'un style</h2> <p>Nous avons pu démarrer notre serveur, qui nous permets déjà d'afficher une carte, avec un style par défaut &quot;Basic preview&quot;. Sur la page d'accueil, vous pouvez voir un lien noté &quot;GL Styles&quot;, qui pointe vers un fichier Json. Si vous vous souvenez de <a href="/2020-08/afficher-carte-openstreetmap">l'article précédent</a>, c'est ce fichier là qui est référencé lors de l'initialisation de la carte avec la librairie Mapbox GL. Vous pouvez utiliser directement ce style, mais on peut aussi en ajouter d'autres.</p> <p>Nous n'allons pas rentrer dans les détails de la définition du style, nous allons nous pour le moment prendre <a href="https://openmaptiles.org/styles/">l'un des styles existant proposés par OpenMapTiles</a>. Histoire de changer un peu de ce dont on a l'habitude, j'ai choisi le style <a href="https://openmaptiles.org/styles/#positron">Positron</a>, vous pouvez <a href="https://github.com/openmaptiles/positron-gl-style/tree/gh-pages">télécharger sa branche <code>gh-pages</code> sur Github</a>.</p> <p>Une fois téléchargé, il va falloir le modifier un peu avant qu'il ne soit utilisable.</p> <p>Première chose, prenez le fichier <code>style-local.json</code>, et cherchez les lignes suivantes :</p> <pre><code>&quot;openmaptiles&quot;: { &quot;type&quot;: &quot;vector&quot;, &quot;url&quot;: &quot;mbtiles://{v3}&quot; } </code></pre> <p>Ici l'url correspond au fichier de données associé. Il faut la remplacer par le nom de notre fichier, mettez donc <code>&quot;url&quot;: &quot;mbtiles://2017-07-03_france_toulouse.mbtiles&quot;</code></p> <p>Ensuite, il faut installer les polices utilisées pour l'affichage des textes sur la carte. Vous pouvez <a href="https://github.com/openmaptiles/fonts/tree/gh-pages">récupérer les fonts sur Github</a>.</p> <p>Maintenant que vous avez récupéré tout ce dont vous avez besoin, uploadez tout ça dans le dossier <code>/data</code> de votre serveur, les fonts dans un sous-dosser <code>fonts</code>, et tout les fichiers du style dans son sous-dossier <code>styles/positron-gl</code>.</p> <p>Dernier point, on va devoir ajouter un fichier de configuration pour faire connaitre toutes ces informations à tileserver. Créez un fichier <code>config.json</code> avec le contenu suivant :</p> <pre><code>{ &quot;options&quot;: { &quot;paths&quot;: { &quot;root&quot;: &quot;&quot;, &quot;fonts&quot;: &quot;fonts&quot;, &quot;styles&quot;: &quot;styles&quot;, &quot;mbtiles&quot;: &quot;&quot; } }, &quot;styles&quot;: { &quot;positron-gl&quot;: { &quot;style&quot;: &quot;positron-gl/style-local.json&quot;, &quot;serve_rendered&quot;:false } }, &quot;data&quot;: { &quot;toulouse&quot;: { &quot;mbtiles&quot;: &quot;2017-07-03_france_toulouse.mbtiles&quot; } } } </code></pre> <p>Ajoutez ce fichier dans <code>/data</code> et redémarrer une nouvelle fois le serveur. Cette fois on est bon, vous pouvez récupérer l'url du fichier &quot;GL Style&quot; et l'utiliser pour afficher vos cartes.</p> <p>Si vous souhaitez aller plus vite dans l'installation de différents styles sur le serveur, j'ai partagé sur Github <a href="https://github.com/glacasa/tileserver-gl-config/">une configuration avec les différents styles et les fonts</a> ; vous pouvez copier les fichiers directement dans votre répertoire <code>/data</code>, il vous faudra juste mettre à jour le fichier mbtiles et ses références.</p> http://blog.lacasa.fr/2022-04/heberger-carte-openstreetmap Mon, 04 Apr 2022 14:00:00 GMT Installer une instance Matomo pour remplacer Google Analytics http://blog.lacasa.fr/2022-02/installer-matomo-remplacer-google-analytics <p>Une décision de la CNIL a fait grand bruit hier dans les communautés d'informaticiens, puisqu'elle estime maintenant que <a href="https://www.cnil.fr/fr/utilisation-de-google-analytics-et-transferts-de-donnees-vers-les-etats-unis-la-cnil-met-en-demeure">l'utilisation de Google Analytics est illégale</a> en Europe. En effet, lorsque un site web intègre ce service, Google récupère et analyse les données personnelles des visiteurs, sans aucun respect pour le RGPD.</p> <p>Cette décision me donne l'occasion de reparler d'un sujet qui me tient à cœur : l'utilisation de services décentralisés et respectueux des utilisateurs.<br /> <a href="https://www.cnil.fr/fr/cookies-solutions-pour-les-outils-de-mesure-daudience">La CNIL propose plusieurs solutions de mesure d'audience</a>, je vous propose aujourd'hui de voir comment installer et configurer l'une d'entre elles : <a href="https://matomo.org/">Matomo</a>.</p> <h2>Installation de Matomo</h2> <p>Matomo est un outil de mesure d'audience open-source. Si vous ne souhaitez pas vous embêter avec la partie technique, vous pouvez directement vous inscrire sur le service <a href="https://matomo.org/start-free-analytics-trial/">Matomo Cloud</a>, et passer directement à la partie &quot;Conformité RGPD&quot; plus bas.</p> <p>De mon côté, je préfère utiliser la version on-premise, nous allons donc voir ça ensemble.<br /> Pour installer Matomo, il faut un hébergement PHP avec une base de données MySQL. Si vous n'avez pas déjà ça sous la main, un hébergement de ce type commence à <a href="https://www.ovhcloud.com/fr/web-hosting/personal-offer/">2,99€ HT par mois chez OVH</a>.</p> <p>Commencez <a href="https://matomo.org/download/">par télécharger Matomo</a>, et vous aurez un fichier zip qu'il vous suffira de décompresser et d'envoyer par FTP sur votre hébergement.</p> <p>Il vous suffit ensuite d'aller sur le site, et suivre les différentes étapes pour configurer l'accès à la base de données et ajouter votre premier site. Je ne vais pas rentrer dans les détails ici, il n'y a rien de compliqué - <a href="https://matomo.org/docs/installation/">la documentation d'installation se trouve par là</a>.</p> <h2>Configuration dans Clever Cloud</h2> <p><strong>Update</strong>: Si vous souhaitez déployer Matomo dans Clever Cloud, <a href="https://www.clever-cloud.com/doc/deploy/addon/matomo/">c'est maintenant géré nativement</a>. Choisissez simplement l'add-on <em>Matomo Analytics</em> et laissez vous guider.</p> <p>Comme <a href="/2021-11/jai-migre-mon-blog-chez-clever-cloud">pour mon blog</a>, j'ai décidé d'installer mon instance chez <a href="https://www.clever-cloud.com/">Clever Cloud</a>, et nous allons vois ensemble comment optimiser notre déploiement sur ce cloud.</p> <p>On va profiter de la configuration automatique de la base de données par des variables d'environnement. Pour ce faire, récupérer ce plugin <a href="https://github.com/glacasa/plugin-EnvironmentVariablesCleverCloud/releases/tag/1.0.0">EnvironmentVariablesCleverCloud</a>, et dézippez son contenu dans le dossier <code>/plugins</code> de votre installation Matomo. Puis depuis l'interface d'administration, allez dans &quot;Système&quot; -&gt; &quot;Composants&quot; pour l'activer.<br /> Dès que c'est fait, Matomo récupèrera sa configuration MySQL directement depuis l'environnement Clever Cloud, ce qui sera utile si vous souhaitez plus tard prendre une instance plus grande.</p> <p>Dernier point : le nombre de connexion simultanées à la base de données est limité, et si vous avez plusieurs utilisateurs en même temps, vous aurez des erreurs. Pour éviter ça, on va activer <a href="https://www.clever-cloud.com/doc/deploy/addon/mysql/proxysql/">ProxySQL</a>, qui va gérer un pool de connexions à la base de données, et s'assurer de ne pas dépasser les limites. Il suffit d'ajouter 2 variables d'environnement :</p> <pre><code>CC_ENABLE_MYSQL_PROXYSQL=true CC_MYSQL_PROXYSQL_MAX_CONNECTIONS=10 </code></pre> <p>La première pour activer ProxySQL, la seconde pour définir le nombre de connection maximum à ouvrir : mettez la valeur maximale autorisée par l'instance MySQL que vous avez choisie (ou un peu moins si vous souhaitez vous connecter directement à la base).</p> <h2>Conformité RGPD</h2> <p>Le fait d'utiliser Matomo ne suffit pas à lui seul à respecter le RGPD. Par contre, configuré correctement, vous pourrez l'ajouter à vos sites web sans avoir besoin d'ajouter un de ces horribles bandeau de consentement.</p> <p>La CNIL décrit en détail comment faire dans son <a href="https://www.cnil.fr/sites/default/files/atoms/files/matomo_analytics_-_exemption_-_guide_de_configuration.pdf">Guide de configuration Matomo Analytics</a>, que je vous conseille de lire ; nous allons voir rapidement les principaux points ensemble :</p> <ul> <li>Désactiver les exports de données :<br /> dans l'administration, section &quot;Système&quot; -&gt; &quot;Paramètres généraux&quot;, cochez la cas «Désactiver le journal des visites et le profil du visiteur»</li> <li>Proposer un opt-out :<br /> il faut permettre aux visiteur de refuser le tracking, en ajoutant une iframe vers une url spéciale de Matomo :<br /> <code>https://stats.monsite.com/index.php?module=CoreAdminHome&amp;action=optOut</code></li> </ul> <p>Le document de la CNIL propose aussi plusieurs points à vérifier, si vous n'avez pas touché aux paramètres par défaut de Matomo, ils sont déjà préconfigurés correctement.</p> <p>Une fois que vous avez fait tout ça, il ne vous reste plus qu'à supprimer les scripts de Google Analytics, et vous pourrez enfin dire fièrement à vos utilisateurs que le respect de leur vie privée est une priorité.<br /> Profitez-en aussi pour vérifier les autres dépendances à des services externes que vous pourriez avoir sur votre site. <a href="https://www.nextinpact.com/article/27855/105881-kimetrak-quels-sites-multiplient-services-pistage">L'extension Kimetrak</a> pour navigateurs peut vous aider à faire le point.<br /> Je pense notamment à des Google Fonts <a href="https://google-webfonts-helper.herokuapp.com/fonts">que vous pourriez héberger sur vos serveurs</a> plutôt que de pointer directement sur ceux de Google. Vous pouvez aussi <a href="/2020-08/afficher-carte-openstreetmap">remplacer l'affichage des cartes Google Maps par celles d'OpenStreetMap</a>.</p> http://blog.lacasa.fr/2022-02/installer-matomo-remplacer-google-analytics Fri, 11 Feb 2022 14:00:00 GMT J'ai déployé des apps chez Clever Cloud http://blog.lacasa.fr/2021-11/jai-deploye-des-apps-sur-clever-cloud <p>J'ai découvert il y a peu le service d'hébergement cloud français <a href="https://www.clever-cloud.com/">Clever Cloud</a>, quelques jours après avoir lu <a href="https://www.champeau.info/blog/2021/09/25/pourquoi-je-defends-la-souverainete-numerique-des-europeens/">l'article de Guillaume Champeau sur la souveraineté numérique</a>.</p> <p>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.</p> <p>Donc en voyant un service français qui promettait un déploiement facile de mes applis .NET, je me suis empressé de tester.</p> <p>Voici mes quelques petits retours, à l'usage des développeurs .NET</p> <h2>Le service</h2> <p>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.<br /> 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.</p> <p>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.</p> <p>Ensuite, tout se configure à l'aide de variable d'environnement, autant les options de déploiement que les paramètres de notre application.</p> <h2>Déploiement d'une application</h2> <p>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 <code>dotnet publish</code> directement.</p> <p>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' <code>APP_FOLDER</code>) et espérer que le <code>dotnet publish</code> fonctionnera bien.</p> <p>Il est néanmoins possible de se brancher sur <a href="https://www.clever-cloud.com/doc/develop/build-hooks/">différents Hooks</a>, 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.</p> <h3>HTTPS</h3> <p>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.<br /> Configurez le domaine dans la console Clever Cloud, faites pointer vos DNS, et attendez quelques minutes : c'est tout bon.</p> <p>Il est aussi possible d'utiliser vos propres certificats si vous préférez, il faudra passer par l'API pour l'installer.</p> <h2>Les Addons</h2> <p>É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.</p> <p>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.</p> <h3>Stockage</h3> <p>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 <code>AWSSDK.S3</code>, pour s'y connecter de la manière suivante :</p> <pre><code> private AmazonS3Client GetS3Client() { var host = &quot;https://&quot; + configuration[&quot;CELLAR_ADDON_HOST&quot;]; var s3config = new AmazonS3Config { ServiceURL = host }; var accesskey = configuration[&quot;CELLAR_ADDON_KEY_ID&quot;]; var secretkey = configuration[&quot;CELLAR_ADDON_KEY_SECRET&quot;]; var credentials = new BasicAWSCredentials(accesskey, secretkey); var client = new AmazonS3Client(credentials, s3config); return client; } </code></pre> <p>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.</p> <h3>Bases de données</h3> <p>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.</p> <p>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.</p> <p>Entity Framework Core est cependant parfaitement compatible avec ces deux SGBD, rien de bloquant donc si on démarre de nouveaux développements.</p> <p>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.</p> <h2>Conclusion</h2> <p>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.<br /> 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.</p> http://blog.lacasa.fr/2021-11/jai-deploye-des-apps-sur-clever-cloud Mon, 01 Nov 2021 14:00:00 GMT Afficher une carte grâce aux données OpenStreetMap http://blog.lacasa.fr/2020-08/afficher-carte-openstreetmap <p>Cette semaine est sorti le jeu Flight Simulator 2020, et certains joueurs <a href="https://twitter.com/liamosaur/status/1296305262144364544">se sont amusés de la présence d'un immense monolithe au nord de Melbourne</a>.<br /> 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.</p> <p><a href="https://www.openstreetmap.org">OpenStreetMap</a>, 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.<br /> 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.</p> <p>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.</p> <h2>Licence</h2> <p>Avant de commencer, on va voir rapidement la partie légale.</p> <p>Les données brutes sont distribuées sous la licence libre <a href="https://opendatacommons.org/licenses/odbl/1-0/">Open Data Commons Open Database License (ODbL)</a>. Comme indiqué sur <a href="https://www.openstreetmap.org/copyright">la page copyright d'OpenStreetMap</a>, 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.<br /> 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 <a href="https://creativecommons.org/licenses/by-sa/2.0/">Creative Commons CC-BY-SA 2.0</a> - les conditions d'utilisation sont globalement les mêmes que pour les données brutes : n'oubliez pas de mettre le copyright.</p> <h2>Affichage d'une carte avec Leaflet</h2> <p>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.<br /> 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, <a href="https://wiki.openstreetmap.org/wiki/Raster_tile_providers">de nombreux services proposent d'utiliser leurs serveurs</a> - 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.</p> <p>Mon choix pour cet article s'est porté sur celui de <a href="https://www.openstreetmap.fr/fonds-de-carte/">l'association OpenStreetMap France</a>.</p> <p>On va maintenant utiliser une librairie JavaScript pour l'affichage. <a href="https://wiki.openstreetmap.org/wiki/FR:D%C3%A9ployer_sa_propre_Slippy_Map">De nombreuses solutions existent</a>, les plus utilisées sont <a href="http://openlayers.org/">OpenLayers</a> et <a href="http://leafletjs.com/">Leaflet</a> ; j'utilise ce dernier dans cet exemple.</p> <p>Pour afficher la carte, créer un div qui la contiendra, en lui précisant obligatoirement une taille.<br /> Ensuite, vous pouvez initlialiser la carte, avec cet exemple de code :</p> <pre><code>&lt;div id=&quot;mapDiv&quot; style=&quot;width:800px; height: 400px;&quot;&gt;&lt;/div&gt; &lt;script&gt; // Initialisation de la carte var map = L.map('mapDiv'); // Initialisation des tuiles var tileUrl = &quot;https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png&quot;; var copyright = '&amp;copy; Contributeurs de &lt;a href=&quot;https://www.openstreetmap.org/&quot;&gt;OpenStreetMap&lt;/a&gt;'; L.tileLayer(tileUrl, { attribution: copyright, maxZoom: 18, tileSize: 512, zoomOffset: -1, }).addTo(map); // On zoome sur Toulouse map.setView([43.60, 1.44], 10); &lt;/script&gt; </code></pre> <p>Ce qui nous donne le résultat suivant :</p> <div id="leaflet" class="maps"></div> <br/> <p>Vous pouvez ensuite afficher toutes sorte d'informations sur la carte ou intéragir avec l'utilisateur ; je vous laisse jete un œil à <a href="https://leafletjs.com/reference-1.6.0.html">la documentation</a> pour ça.</p> <h2>Affichage vectoriel avec OpenMapTiles</h2> <p>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 <a href="https://openmaptiles.org/">OpenMapTiles</a>, en proposant un système de tuiles vectorielles.<br /> 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 <a href="https://www.qwant.com/maps/">Qwant Maps</a>.</p> <p>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.</p> <p>Pour afficher une carte vectorielle, j'utilise cette fois les serveurs de <a href="https://www.maptiler.com/">MapTiler</a>, et la librairie <a href="https://docs.mapbox.com/mapbox-gl-js/api/">Mapbox GL JS</a>. OpenLayers et Leaflet ne gèrent pas l'affichage vectoriel nativement, mais peuvent fonctionner avec des plugins.</p> <p>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 :</p> <pre><code>&lt;div id=&quot;mapDiv&quot; style=&quot;width:800px; height: 400px;&quot;&gt;&lt;/div&gt; &lt;script&gt; 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 }); &lt;/script&gt; </code></pre> <p>Vous pouvez ainsi voir le résultat - et voir la différence avec la carte précédente :</p> <div id="openmaptiles" class="maps"></div> <p>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.</p> <p>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.</p> <script> initLeaflet("leaflet", "https://a.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"); initOpenMapTiles("openmaptiles", "https://api.maptiler.com/maps/bright/style.json?key=PnYoqSStQ1seC1agfPPl"); </script> http://blog.lacasa.fr/2020-08/afficher-carte-openstreetmap Fri, 21 Aug 2020 12:00:00 GMT Utiliser un composant Blazor dans une application ASP.NET Core MVC http://blog.lacasa.fr/2019-07/utiliser_Blazor_dans_MVC <p>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 <a href="https://docs.microsoft.com/fr-fr/aspnet/core/blazor/?view=aspnetcore-3.0">la documentation de Blazor</a>.</p> <p>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.</p> <p>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.</p> <h2>Migration vers ASP.NET Core 3</h2> <p>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.</p> <p>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, <a href="https://docs.microsoft.com/fr-fr/aspnet/core/migration/22-to-30?view=aspnetcore-3.0&amp;tabs=visual-studio">lisez la documentation</a> pour effectuer la migration, notamment le passage sur la migration du code de routage pour utiliser <code>.UseEndpoints()</code>.</p> <h2>Configuration de Blazor dans le Startup.cs</h2> <p>Pour activer Blazor dans le projet, il y a 2 modifications à faire dans le Startup.cs :</p> <ul> <li>Dans la méthode <code>ConfigureServices</code>, ajoutez <code>services.AddServerSideBlazor();</code> : enregistrement du service Blazor</li> <li>Dans la méthode <code>Configure</code>, allez dans le <code>UseEndoints</code> et ajoutez <code>endpoints.MapBlazorHub();</code> : configuration du hub SignalR utilisé par Blazor</li> </ul> <p>On se retrouve avec le code qui ressemble à ça :</p> <pre><code>public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddServerSideBlazor(); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler(&quot;/Home/Error&quot;); } app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints =&gt; { endpoints.MapControllerRoute( name: &quot;default&quot;, pattern: &quot;{controller=Home}/{action=Index}/{id?}&quot;); endpoints.MapBlazorHub(); }); } </code></pre> <h2>Ajout d'un composant Blazor dans une page</h2> <p>Pour mon exemple, j'ai repris le composant Counter du template Blazor, sans l'attribut @page</p> <pre><code>&lt;h1&gt;Counter&lt;/h1&gt; &lt;p&gt;Current count: @currentCount&lt;/p&gt; &lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;IncrementCount&quot;&gt;Click me&lt;/button&gt; @code { int currentCount = 0; void IncrementCount() { currentCount++; } } </code></pre> <p>Puis on peux référencer le composant depuis notre vue MVC :</p> <pre><code>@(await Html.RenderComponentAsync&lt;BlazorInMvc.Components.Counter&gt;(RenderMode.Server)) </code></pre> <p>Vous pouvez lancer la page : vous verrez le composant s'afficher avec le &quot;Current count&quot; à 0, et le bouton. Mais si vous cliquez dessus, rien ne se passe. Il faut ajouter un fichier javascript pour établir la connexion SignalR :</p> <pre><code>@section Scripts{ &lt;script src=&quot;~/_framework/blazor.server.js&quot;&gt;&lt;/script&gt; } </code></pre> <p>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 <code>/Home/Counter</code>, le dossier est <code>/Home</code>. J'ajoute donc à ma page :</p> <pre><code>@section Header{ &lt;base href=&quot;~/Home&quot; /&gt; } </code></pre> <p>Maintenant vous pouvez retester, et le composant fonctionne : les clics sur le bouton mettent bien à jour le compteur.</p> <h2>Utilisation de la navigation Blazor</h2> <p>À 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.</p> <p>J'ai 2 pages MVC : <code>/Home</code> et <code>/Home/Counter</code> (même si l'une contient un composant Blazor, c'est toujours une page MVC), et je veux créer 2 pages Blazor : <code>/Nav/Page1</code> et <code>/Nav/Page2</code>, qui fonctionneront en mode SPA lorsqu'on navigue de l'une à l'autre (et seulement entre ces 2).</p> <p>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 <code>/Nav</code> pointent vers la même Action.</p> <p>NavController.cs :</p> <pre><code>public class NavController : Controller { public IActionResult Index() { return View(); } } </code></pre> <p>Startup.cs :</p> <pre><code>app.UseEndpoints(endpoints =&gt; { endpoints.MapControllerRoute( name: &quot;blazor&quot;, pattern: &quot;Nav/{page?}/{id?}&quot;, defaults: new { controller = &quot;Nav&quot;, action = &quot;Index&quot; } ); endpoints.MapControllerRoute( name: &quot;default&quot;, pattern: &quot;{controller=Home}/{action=Index}/{id?}&quot;); endpoints.MapBlazorHub(); }); </code></pre> <p>L'action Index va contenir un composant Blazor, qui servira de routeur pour les 2 pages :</p> <p>Nav.razor :</p> <pre><code>&lt;Router AppAssembly=&quot;typeof(Startup).Assembly&quot;&gt; &lt;Found Context=&quot;routeData&quot;&gt; &lt;RouteView RouteData=&quot;routeData&quot; /&gt; &lt;/Found&gt; &lt;NotFound&gt; &lt;h1&gt;Page not found&lt;/h1&gt; &lt;/NotFound&gt; &lt;/Router&gt; </code></pre> <p>Puis les composants Blazor correspondant à nos pages :</p> <pre><code>@page &quot;/Nav/Page1&quot; @using Microsoft.AspNetCore.Components.Routing &lt;h3&gt;Page1&lt;/h3&gt; &lt;NavLink href=&quot;/Nav/Page2&quot;&gt;Aller à la page 2&lt;/NavLink&gt; </code></pre> <p>et</p> <pre><code>@page &quot;/Nav/Page2&quot; @using Microsoft.AspNetCore.Components.Routing &lt;h3&gt;Page2&lt;/h3&gt; &lt;NavLink href=&quot;/Nav/Page1&quot;&gt;Aller à la page 1&lt;/NavLink&gt; </code></pre> <p>Les composants commencent par l'attribut @page, qui permets de définir à quelle url le composant doit répondre.</p> <p>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.</p> <h2>Code source</h2> <p>Vous pouvez récupérer le code source de cet article sur ma page Github : <a href="https://github.com/glacasa/Blog-BlazorInMvc">https://github.com/glacasa/Blog-BlazorInMvc</a>.</p> <p>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.</p> http://blog.lacasa.fr/2019-07/utiliser_Blazor_dans_MVC Wed, 31 Jul 2019 12:00:00 GMT packages.config vs PackageReference - le fonctionnement de Nuget avec le nouveau format csproj http://blog.lacasa.fr/2018-12/PackageReference-nuget-nouveau-format-csproj <p>Si vous développez un projets sur le framework .NET &quot;classique&quot;, et que vous avez décidé de passer à <a href="https://docs.microsoft.com/en-us/dotnet/standard/net-standard">.NET Standard</a> pour les projets liés, vous avez peut-être eu droit à une erreur <code>FileNotFoundException</code> au chargement d'une dll (utilisée par votre projet .NET Standard) - vous obligeant à référencer le package Nuget en question directement depuis l'application appellante.</p> <p>Depuis quelques temps, les développeurs .NET peuvent utiliser un nouveau format de fichier .csproj (et pour les développeurs Visual Basic, tout ce que j'explique ici à propos des csproj s'applique aussi aux vbproj), dans les projets .NET Core ou .NET Standard.<br /> Voici par exemple les 2 formats de csproj, l'un étant un projet console en .NET classique, l'autre en .NET Core :</p> <p><img src="/assets/nouveau-csproj.png" alt="Comparaison des anciens et nouveaux formats csproj" /></p> <p>Outre le gain évident en terme de lisibilité, le nouveau format apporte une nouvelle gestion des packages Nuget. Voyons comment tout ça fonctionne maintenant.</p> <h2>L'ancien fonctionnement : le fichier packages.config</h2> <p>Si vous êtes développez avec l'ancien format csproj, lorsque vous référencez un package Nuget, il est ajouté dans le fichier <code>packages.config</code>, qui se trouve à la racine du projet.</p> <pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt; &lt;packages&gt; &lt;package id=&quot;Newtonsoft.Json&quot; version=&quot;12.0.1&quot; targetFramework=&quot;net471&quot; /&gt; &lt;/packages&gt; </code></pre> <p>Si le package en référence d'autres, toutes les dépendances seront ajoutées dans ce fichier. On peut donc très vite arriver à un assez grand nombre de packages référencés ici.</p> <p>Le fonctionnement de Nuget est ensuite très simple : il va télécharger le package et le mettre dans un dossier <code>packages</code>, situé au même niveau que le sln. Il va ensuite modifier le <code>csproj</code> pour ajouter la référence vers la ou les dll. Ces dll seront ensuite copiées dans le répertoire de destination en même temps que le projet.</p> <h2>Le nouveau fonctionnement : <PackageReference></h2> <p>La refonte du format csproj a été l'occasion d'améliorer la gestion des packages, et donc de gérer Nuget nativement. Cette fois, plus de fichier <code>packages.config</code>, si vous référencez un package il est directement ajouté dans le csproj en tant que package :</p> <pre><code>&lt;Project Sdk=&quot;Microsoft.NET.Sdk&quot;&gt; &lt;PropertyGroup&gt; &lt;OutputType&gt;Exe&lt;/OutputType&gt; &lt;TargetFramework&gt;netcoreapp2.1&lt;/TargetFramework&gt; &lt;/PropertyGroup&gt; &lt;ItemGroup&gt; &lt;PackageReference Include=&quot;Newtonsoft.Json&quot; Version=&quot;12.0.1&quot; /&gt; &lt;/ItemGroup&gt; &lt;/Project&gt; </code></pre> <p>Contrairement à l'ancien csproj, où toutes les références étaient des liens vers des dll, ici on sait que la référence vient de Nuget quand on utilise la balise <code>&lt;PackageReference&gt;</code>.</p> <p>Autre différence avec l'ancien format : nous n'avons plus le dossier <code>packages</code>. Maintenant, les dll sont téléchargées dans le profil utilisateur (allez jeter un œil dans <code>%userprofile%\.nuget\packages</code> sur votre disque dur pour voir la liste), et elles ne sont pas copiées dans l'output lors de la compilation.</p> <h2>Utiliser les deux à la fois</h2> <p>Si vous avez des projets .NET Standard référencés par un projet .NET classique, vous allez vous retrouvez avec les 2 systèmes à la fois, qui ne sont pas vraiment compatibles, et c'est ce qui va causer la <code>FileNotFoundException</code>.</p> <p>Supposons que votre projet .NET Standard référence <code>Newtonsoft.Json</code>. Il utilise le nouveau Nuget, et ne va pas copier la dll dans l'output.<br /> Votre projet .NET classique lui utilise toujours l'ancien système de références de projets : il va récupérer l'output de l'autre projet, mais ne récupèreras donc pas <code>Newtonsoft.Json</code>. Et donc, lorsque vous aurez besoin de sérialiser des données à l'exécution, ça plante.</p> <p><img src="/assets/FileNotFoundException.png" alt="System.IO.FileNotFoundException" /></p> <p>Un moyen simple de réparer ce problème est de simplement ajouter le package Nuget dans le projet principal, pour être sûr qu'il le récupère. Mais il y a mieux.</p> <h2>Utiliser <code>PackageReference</code> dans l'ancien csproj.</h2> <p>En fait, il est possible d'utiliser les <PackageReference> dans les anciens csproj, la documentation se trouve ici :<br /> <a href="https://docs.microsoft.com/en-us/nuget/reference/migrate-packages-config-to-package-reference">Migrate from packages.config to PackageReference</a>.</p> <p>Attention, ça ne fonctionne que dans Visual Studio 2017 (v 15.7 minimum), et les projets C++ et ASP.NET ne sont pas encore supportés.<br /> Pour utiliser <code>PackageReference</code>, il suffit d'ajouter <code>&lt;RestoreProjectStyle&gt;PackageReference&lt;/RestoreProjectStyle&gt;</code> dans le csproj :</p> <pre><code>&lt;/PropertyGroup&gt; &lt;RestoreProjectStyle&gt;PackageReference&lt;/RestoreProjectStyle&gt; &lt;/PropertyGroup&gt; </code></pre> <p>Attention, si vous avez déjà un <code>packages.config</code>, il faut désinstaller tous les packages avant de les remettre. Visual Studio 2017, vous propose de le faire automatiquement, il suffit de faire un clic droit sur le fichier <code>packages.config</code>, et cliquer sur &quot;Migrate packages.config to PackageReference&quot;.</p> <p><img src="/assets/migrate-nuget.png" alt="Migration de packages.config vers PackageReference" /></p> <p>Si tout se passe bien, ça marchera du premier coup (sinon bon courage pour débuguer les conflits de version). Une fois sur le nouveau Nuget, vous ne devriez plus avoir de soucis de fichiers manquants.</p> http://blog.lacasa.fr/2018-12/PackageReference-nuget-nouveau-format-csproj Thu, 06 Dec 2018 21:00:00 GMT