.Net Core 2.1 – HoNoSoFt https://blog.honosoft.com Blog & Roll Fri, 11 Jan 2019 12:52:34 +0000 en-CA hourly 1 https://wordpress.org/?v=6.8.10 https://blog.honosoft.com/wp-content/uploads/2018/06/logo.png .Net Core 2.1 – HoNoSoFt https://blog.honosoft.com 32 32 Canvas Chart ➡️ Snapshot ➡️ Manipulate ➡️ Send it to your confluence page (or elsewhere) https://blog.honosoft.com/2019/01/11/canvas-chart-%e2%9e%a1%ef%b8%8f-snapshot-%e2%9e%a1%ef%b8%8f-manipulate-%e2%9e%a1%ef%b8%8f-send-it-to-your-confluence-page-or-elsewhere/?utm_source=rss&utm_medium=rss&utm_campaign=canvas-chart-%25e2%259e%25a1%25ef%25b8%258f-snapshot-%25e2%259e%25a1%25ef%25b8%258f-manipulate-%25e2%259e%25a1%25ef%25b8%258f-send-it-to-your-confluence-page-or-elsewhere https://blog.honosoft.com/2019/01/11/canvas-chart-%e2%9e%a1%ef%b8%8f-snapshot-%e2%9e%a1%ef%b8%8f-manipulate-%e2%9e%a1%ef%b8%8f-send-it-to-your-confluence-page-or-elsewhere/#respond Fri, 11 Jan 2019 12:39:37 +0000 https://blog.honosoft.com/?p=426 Continue Reading]]> Today, let’s create a connection between one of your app using ChartJs + Confluence and send or synchronize a chart. The flow with Confluence server used will be with the basic authentication. In the case you want to build a real plugin/extension to Confluence, please follow the proper guide about it. In this blog post, the basic authentication is simply a mean to an end for quick prototyping.

A complete example of a standalone project can be found on Github (https://github.com/Nordes/HoNoSoFt.PushChartToConfluence.Sample).

Introduction

Like I said, we will have an application serving some charts using the Canvas (HTML5) technology. We will then resize, take a snapshot and then transmit this file to our backend server. Then it will connect to confluence, look at the existing attachments and if it exists it will update using a comment or in the case where the file does not exists, we will be posting a new file. It is nice to update the file, but why not add the file (image) to the page if it is not already there. 🙂

Proposal using a chart

Note that we’re going to add the chart, but the same flow could be used to also update some comment/text. As long as you are familiar with html syntax, you should be able to do something good.

Pre-requisites before starting

  1. If you already have a confluence server or follow the official installation guide from Confluence, skip to #3 (Create a small application)
  2. For the docker users, let’s go towards the evaluation “just because”
    1. docker pull atlassian/confluence-server:latest
      1. About 700mo
    2. While it download, you can start by requesting a KEY
      1. https://my.atlassian.com/license/evaluation
      2. Select a Confluence server license and keep that page open until the docker image is ready.
    3. Start the docker image
      • Command: docker run -v $volume$:/var/atlassian/application-data/confluence --name="confluence" -d -p 8090:8090 -p 8091:8091 atlassian/confluence-server
        • $volume$: C:/demo/your/confluence/home
      • Open site: http://localhost:8090/
    4. During installation, fetch the Server ID and input it in the evaluation page.
      1. Follow the instructions
        1. Select trial
        2. Get the Server ID
        3. Go on your atlassian page already open and paste it
        4. Generate your key
        5. Copy/paste your key in the form within the confluence page
        6. Click next (wait a little) and voilà
        7. Setup a user or two and then let’s start
  3. Create a small application with ChartJs (or use the one built in the previous post )

Back-End

In the back-end, we will first be receiving the image to at least test our local upload between front-end and back-end. After, we will start integrating the Confluence connexion.

Dotnet ImagesController: Receive Images (Part 1)

Within Dotnet Core, we can receive files or forms controls using the IFormCollection. I don’t think it is common to receive such thing, but when talking about receiving files, this comes handy. The client (JavaScript) will then create a collection and send it using a multipart/form-data. In there we can find many things, however what we will only using here are the files (((IFormCollection)MyFormCollection).Files).

Let’s create a controller named ImagesController we will receive the files as stream and then copy those files to a temporary folders. If you already played around with file streaming you can probably skip this part.

[Route("api/[controller]")]
[ApiController]
public ImagesController : ControllerBase {
    // .. some constructor stuff

    /// <summary>
    /// This will save the image in your local "temp" folder.
    /// </summary>
    [HttpPost]
    [ProducesResponseType((int)HttpStatusCode.Created)]
    public async Task<IActionResult> PostAsync(IFormCollection formCollection)
    {
        var files = formCollection.Files;
        long size = files.Sum(f => f.Length);
        List<string> fileList = new List<string>();

        foreach (var formFile in files)
        {
            // full path to file in temp location
            var filePath = Path.GetTempFileName();
            if (formFile.Length > 0)
            {
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    // Save to file... We could remove the await, have a List<Task<..>> and then do a Task.WhenAll(myList)
                    await formFile.CopyToAsync(stream);
                    fileList.Add(filePath);
                }
            }
        }

        // Example of processed uploaded files returning details of the new file + original request.
        // You shouldn't rely on (or) trust the FileName property without validation.
        return Ok(new { count = files.Count, size, fileList });
    }
}

This is named Part 1 since we will come back in that code in order to add some code to send to confluence.

Front-End

Do the snapshot

From JavaScript, we have two methods that are quite handy to take snapshot of a Canvas. The first one is “toBlob(…)” and the second is “toDataUrl(…)“. While “toBlob” is not supported everywhere, I find it more useful for the demo, otherwise feel free to use “toDataUrl” for all browsers supports except Edge and then convert that base64 into a binary data (image/png). In case you go towards the “toDataUrl” method, don’t forget that you will have to transform the data once it’s server side for something readable (binary) as an image/png.

You will see in the next sub-section how to start from a ChartJs chart which you’d like to push to your back-end having a resolution of 1200px wide. Don’t forget that if you use an adaptive screen UX, it will not be displayed as 1200px until you do a snapshot for a fraction of a second.

How does the resizing work?

As you already know, a ChartJs canvas does not resize automatically at your wishes and if you want a snapshot, you are required to hack your way. Next, we are going to have the following flow:

Interesting part in the JS, I will skip the trivial step of generating a chart since you’re I suspect that you’re already able to do so.

// Some code (I used VueJs, but other language should look alike)

snapshot: function () {
  // Resize to desired size, bigger it is, more heavy will be the blob file.
  this.$refs.chartContainer.style.width = '1200px'
  this.chart.resize()
  var ctx = this

  // Do a snapshot
  this.$refs.chart.toBlob(function (blob) {
    // Resize back to original size
    ctx.$refs.chartContainer.style.width = ""
    ctx.chart.resize()

    // Prepare the form post
    // The file name used will be the chart title, but depending on your case, you
    // might want to have something more precise based on parameter (hash, encoding, something). 
    var filename = `${ctx.chartConfig.options.title.text}.png`
    var data = new FormData()
    data.append('file', blob, filename)

    const config = {
        headers: { 'content-type': 'multipart/form-data' }
    }

    // Post the file to your backend
    ctx.$http.post('/api/images', data, config)
    // Later in the article it should become as
    // ctx.$http.post('/api/images/confluence/{desiredPageId}', data, config)
  }, "image/png", 0.95);
}

// Some code

As you can see, I resize to 1200px => snapshot (toBlob) => resize back to original => send the file as multipart/form-data to the api.

The file should normally be created within your temp folder and the exact location will come back through the API. It is also part of the current data contract. Now that you have that, you can consider yourself ready for the next step, which is to transfer that buffered binary data directly to confluence using their API’s.

Back-end Part 2 – Use confluence API’s

Now that we know we can receive file and save it, we now simply need to use the Confluence API’s from Atlassian. The related documentation for that is:

API’s that we’re going to use for this demo are:

  • [GET] api/content/{pageId}/child/attachment?filename={formFile.FileName}&expand=version
    • Search/Retrieve the existing attachment. In case it does not exists, it will sends back an empty array.
  • [POST] api/content/{pageId}/child/attachment/{attachmentData.Id}/data
    • Update in case of existing attachment
  • [POST] api/content/{pageId}/child/attachment
    • Create the attachment resource if it was not already existing
  • [GET] api/content/{pageId}?expand=version,body.storage
    • Retrieve the details on the current page, especially the body specified in readable/editable way (storage). The version is also mandatory when you want to update the page.
  • [PUT] api/content/{pageId}
    • Api used in order to update the confluence page.

Update the ImagesController to forward to Confluence

There’s maybe more code than required in the controller. The proper approach would be to use a IDataProvider injected (for UT) and then implement the provider using the IHttpClientFactory. That way, all would be testable and also it would also put the logic where it should be. However, let’s put all for now in one place and please adapt for your needs.

// some code in the controller

        /// <summary>
        /// Receive 1 or more images to be sent to Confluence server.
        /// </summary>
        /// <param name="formCollection">The form data (only files are being consumed)</param>
        /// <param name="pageId">The confluence page Id</param>
        /// <remarks>
        /// More details can be found at https://developer.atlassian.com/server/confluence/confluence-rest-api-examples/
        /// </remarks>
        [HttpPost("confluence/{pageId}")]
        [ProducesResponseType((int)HttpStatusCode.Created)]
        public async Task<IActionResult> PostToConfluence(IFormCollection formCollection, int pageId)
        {
            var files = formCollection.Files;
            long size = files.Sum(f => f.Length);
            List<string> fileList = new List<string>();
            var forwardAttachmentTasks = new List<Task<FileTransferResult>>();

            foreach (var formFile in files)
            {
                if (formFile.Length > 0)
                {
                    forwardAttachmentTasks.Add(ForwardFileToConfluence(pageId, formFile));
                }
            }

            // In case we had multiple tasks at the same time.
            await Task.WhenAll(forwardAttachmentTasks.ToArray()).ConfigureAwait(false);
            await UpdatePage(pageId, forwardAttachmentTasks).ConfigureAwait(false);

            return StatusCode((int)HttpStatusCode.InternalServerError, new { count = files.Count, size, fileList });
        }

        private async Task UpdatePage(int pageId, List<Task<FileTransferResult>> forwardTasks)
        {
            if (forwardTasks.Any())
            {
                // Update the page
                var pageContentResult = await _confluenceHttpClient.GetAsync($"content/{pageId}?expand=version,body.storage");
                var pageContentData = JsonConvert.DeserializeObject<Models.Confluence.Content.ContentStorage>(await pageContentResult.Content.ReadAsStringAsync());

                string newContent = string.Empty;
                foreach (var sentAttachmentTask in forwardTasks)
                {
                    var sentAttachment = await sentAttachmentTask;
                    if (sentAttachment != null && sentAttachment.Results.Any())
                    {
                        // Add the file if not present on the page.
                        var fileName = sentAttachment.Results.First().Title;
                        if (pageContentData.Body.Storage.Value.IndexOf($"<ri:attachment ri:filename=\"{fileName}\" />") == -1)
                        {
                            newContent += $"<h2>You've just pushed: {fileName}</h2><p><ac:image><ri:attachment ri:filename=\"{fileName}\" /></ac:image></p>";
                        }
                        // Else: Nothing to do, it's already on the page somewhere.
                    }
                }

                // Update object (camelCase mandatory, so use a proper serializer in real life scenario)
                var updateQuery = new
                {
                    id = pageContentData.Id,
                    title = pageContentData.Title,
                    status = pageContentData.Status,
                    type = pageContentData.Type,
                    version = new { number = pageContentData.Version.Number + 1 },
                    body = new
                    {
                        storage = new
                        {
                            value = pageContentData.Body.Storage.Value + newContent,
                            representation = "storage"
                        }
                    }
                };

                var result = await _confluenceHttpClient.PutAsJsonAsync($"content/{pageId}", updateQuery);
            }
        }

        private async Task<FileTransferResult> ForwardFileToConfluence(int pageId, IFormFile formFile)
        {
            // Start getting if attachment exists
            var getIfAttachmentExists = _confluenceHttpClient.GetAsync($"content/{pageId}/child/attachment?filename={formFile.FileName}&expand=version").ConfigureAwait(false);
            // While previous request goes on, let's get the file.
            byte[] data;
            using (var br = new BinaryReader(formFile.OpenReadStream()))
            {
                data = br.ReadBytes((int)formFile.OpenReadStream().Length);
            }

            ByteArrayContent bytes = new ByteArrayContent(data);
            MultipartFormDataContent multipartContent = new MultipartFormDataContent();
            multipartContent.Add(bytes, "file", formFile.FileName);

            var attachmentRequestData = await getIfAttachmentExists;
            if (attachmentRequestData.IsSuccessStatusCode && attachmentRequestData.StatusCode == HttpStatusCode.OK)
            {
                // Page exists and no errors...
                var attachmentRequestContent = await attachmentRequestData.Content.ReadAsStringAsync().ConfigureAwait(false);
                var attachmentData = JsonConvert.DeserializeObject<FileSearch>(attachmentRequestContent);

                HttpResponseMessage putAttachmentResponse;
                // Update existing data.
                if (attachmentData.Size == 1)
                {
                    multipartContent.Add(new StringContent($"Automatic update/upload from TestApplication ;)."), "comment");
                    putAttachmentResponse = await _confluenceHttpClient.PostAsync(
                        $"content/{pageId}/child/attachment/{attachmentData.Results.First().Id}/data",
                        multipartContent);

                    // Result is 1 "item"
                    var content = await putAttachmentResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
                    var result = JsonConvert.DeserializeObject<Models.Confluence.Result>(content);

                    return new FileTransferResult() { Results = new Models.Confluence.Result[] { result }, Size = 1 };
                }
                else
                {
                    // Create the attachment
                    multipartContent.Add(new StringContent($"Automatic upload from TestApplication ;)."), "comment");
                    putAttachmentResponse = await _confluenceHttpClient.PostAsync(
                        $"content/{pageId}/child/attachment",
                        multipartContent);

                    // Result is a list of item.
                    var content = await putAttachmentResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
                    return JsonConvert.DeserializeObject<FileTransferResult>(content);
                }
            }

            return default(FileTransferResult);
        }

// some code in the controller

It’s a lot of code. By reading it, it should be really easy to understand. During the file upload to Atlassian Confluence, we add a comment (updated or created) and the version gets updated. That way you could show the changes over time.

What else you could do?

  • Transform the backend in order to have a provider injected
  • Create UT
  • Create IT
  • Change the token used in order to use the official Atlassian flow for plugins
  • Automate everything back-end by creating a scheduled job (not tested, but should be feasible)
    1. Selenium
    2. Image docker + chromium
    3. Execute the javascript using chrome Webdriver
    4. Send the image using a job.
  • Send a SnapShot (not necessarily from Charts) while building your backend app in a pipeline.

Conclusion

Thank you for reading and I hope you have learned something today, or at least enjoyed this article.

]]>
https://blog.honosoft.com/2019/01/11/canvas-chart-%e2%9e%a1%ef%b8%8f-snapshot-%e2%9e%a1%ef%b8%8f-manipulate-%e2%9e%a1%ef%b8%8f-send-it-to-your-confluence-page-or-elsewhere/feed/ 0
Projet en cours – Créer des “Badges” à partir de .Net Core 2.1 pour les statistiques Sonar Qube https://blog.honosoft.com/2018/09/06/creer-des-badges-a-partir-de-net-core-2-1-pour-les-statistiques-sonar-qube/?utm_source=rss&utm_medium=rss&utm_campaign=creer-des-badges-a-partir-de-net-core-2-1-pour-les-statistiques-sonar-qube https://blog.honosoft.com/2018/09/06/creer-des-badges-a-partir-de-net-core-2-1-pour-les-statistiques-sonar-qube/#respond Thu, 06 Sep 2018 12:53:59 +0000 https://blog.honosoft.com/?p=191 Continue Reading]]> Introduction

Comme je suis plutôt occupé ces derniers temps (projet personnel, boulot, les trucs normaux quoi), j’ai tout de même envie de vous parler de mon dernier projet quasi achevé. Une fois terminé, ce sera aussi l’objet d’un blog afin d’expliquer comment ça fonctionne.

Idée

L’idée du projet est simple: Générer des badges pour Sonar Qube. Mais pas que. En fait, si vous utilisez un Sonar Qube protégé avec mot de passe, le plugin existant ne vous permet pas d’afficher vos badges sans que vous vous ayez au préalable connecté sur le site. Du coup, ce que je propose, c’est une application .Net Core 2.1 qui intérragit avec Sonar Qube utilisant une clée API. Cette clée d’API peut être créée à l’aide de votre compte sur les projets que vous pouvez accéder.

Badge It – Sera le nom du projet HoNoSoFt.

Le look

Une fois chargé les badges auront un air de ce qui suit (SVG’s):

Technologies

  • .Net Core 2.1
  • Sonar Qube (API)
    • Peut être installé locallement
  • shields.io (API)
    • Peut être installé locallement

Conclusion

Perso, je crois que ce sera une belle addition à vos builds et vos badges sur serveur locaux (non accessible par internet).

]]>
https://blog.honosoft.com/2018/09/06/creer-des-badges-a-partir-de-net-core-2-1-pour-les-statistiques-sonar-qube/feed/ 0
Net Core + VS Code => Débugage à distance dans un conteneur par tunnel ssh? Why not! https://blog.honosoft.com/2018/07/29/net-core-et-vscode-debugage-a-distance-conteneur-par-tunnel-ssh/?utm_source=rss&utm_medium=rss&utm_campaign=net-core-et-vscode-debugage-a-distance-conteneur-par-tunnel-ssh https://blog.honosoft.com/2018/07/29/net-core-et-vscode-debugage-a-distance-conteneur-par-tunnel-ssh/#respond Sun, 29 Jul 2018 06:50:41 +0000 https://blog.honosoft.com/?p=192 Continue Reading]]> Idée de la tâche à accomplir

Comme vous le savez, des fois il est quasi impossible de faire du débugage local. C’est à ce moment que l’on doit se pencher sur le débugage à distance de processus. Dans tous les cas, faire ça à distance et éviter de se baser que sur les logs et les suppositions, n’est jamais simple. Microsoft à fait quelque chose de vraiment bien en emmenant la possibilité de le faire sur quasi toutes les plateformes dont on peut déployer. De plus Visual Studio Code fonctionne partout (merci Electron). Ici, avec .Net Core 2.1, nous allons debugger sur de Windows vers Linux (ubuntu). Veuillez noter que ça ne s’arrête pas que là et c’est à vous de vous amuser avec vos limitations (des fois proxy, firewall, etc.).

Le but de cet article sera d’utiliser Visual Studio Code, créer un projet et le déployer sur un hôte situé dans un conteneur LXD sur un serveur Linux Ubuntu. Du coup, le schéma ressemble un peu à ce qui suit:

Notre objectif étant de se connecter au conteneur final dans un environnement très sécurisé, nous aurons besoin de:

  • Visual Studio Code (Pourrait aussi éventuellement être Visual Studio 2017)
    • Extension C# .Net (OmniSharp)
  • Git Bash, ou un bash ou vous devrez pouvoir faire de la redirection SSH. (Tunnel)
    • plink peut sans doute aussi fonctionné
  • Un serveur Ubuntu qui exécute des conteneur de type LXD

Évidemment je m’attends de vous que vous aillez quelques connaissance sur Linux, Windows ainsi que sur les clés sécurisé SSH.

Séparation de l’article

  • Qu’est-ce que LXD?
  • Création d’une application .Net Core qui sera utilisé pour votre premier débugage à distance.
  • Création d’un tunnel locale
  • Utilisation de la clé SSH pour la connection au tunnel (Conteneur)
  • Installation de VSDBG
  • Configuration de VS Code (Launch.json)
  • Déploiement de l’application
  • Démarrage de l’application
  • S’attacher au processus par Visual Studio Code
    • Le point d’arrêt devra être touché au moment de l’exécution de ce dernier
  • Conclusion

Qu’est-ce que LXD

Ceci est une question qui à le mérite d’être répondu. Cependant, si vous êtes sur cet article et que vous savez déjà ce qu’est LXD. Veuillez, passer à la prochaine étape.

LXD est tout simplement une solution de conteneur (containter), tout comme Docker, natif sur Ubuntu. Pour les détails complets, vous pouvez aller sur le site officiel de la documentation.

Pour tester un accès à votre conteneur en bash, vous pouvez faire “lxc exec mon-conteneur bash” et du coup vous allez tomber dans le bash de votre conteneur et vous assurer que vous avez un serveur SSH accessible. Lors de votre déploiement, si vous vous connecté par sFTP, le lieu ou résidera le conteneur sera situé sous le répertoire /var/lib/lxd/container/mon-conteneur/fsroot/…/…”.

La solution proposé dans cet article s’adresse à LXD, mais si vos conteneurs Docker ne sont pas directement exposé vers l’extérieur, vous aurez le même processus ou voir très semblable à exécuter.

Création d’une application .Net Core qui sera utilisé pour votre premier débugage à distance

Il y a deux façon de procéder. La première est d’utiliser la ligne de commande et la seconde, la plus populaire, utiliser Visual Studio 2017 et utilisé le template pour créer un projet. Pour vous montrer à quel point c’est simple avec .Net Core 2.1, dans cet article nous procéderons avec la lignes de commandes. Donc, si vous préférez Visual Studio 2017, veuillez sauter quelques étapes ;).

  1. Ouvrir une invite de commande (prompt) ou bien à partir de VS Code ouvrir votre répertoire de démo/test
  2. Déplacez-vous dans votre répertoire de choix si ce n’est pas déjà fait. Dans l’exemple ici présent on prendra c:/RemoteDebugging/
  3. Création du projet de type WebApi (.Net Core 2.1): dotnet new webapi (Si vous voulez, vous pouvez aussi faire un projet de console avec une boucle infinie)
    1. eEUne fois créé, testez en lançant la commande: dotnet run
    2. Le service web démarrera. Depuis la version 2.1, le SSL est donné par défaut. Si vous le voulez, vous pouvez toujours le désactiver.  L’API sera accessible sur: https://localhost:5001/api/values
    3. Un document JSON vous sera affiché et nous pouvons passer à la prochaine étape
    4. Vous devrez aussi stopper votre serveur web (CTRL+C), car pour publier ça ne fonctionnera pas si vous êtes attaché au processus ;).
    5. Exemple de ce que ça donnera (si vous êtes sur un Mac)
      Mac-Air:RemoteDebugging nordes$ dotnet new webapi
      The template "ASP.NET Core Web API" was created successfully.
      
      Processing post-creation actions...
      Running 'dotnet restore' on /Users/nordes/programs/RemoteDebugging/RemoteDebugging.csproj...
        Restoring packages for /Users/nordes/programs/RemoteDebugging/RemoteDebugging.csproj...
        Generating MSBuild file /Users/nordes/programs/RemoteDebugging/obj/RemoteDebugging.csproj.nuget.g.props.
        Generating MSBuild file /Users/nordes/programs/RemoteDebugging/obj/RemoteDebugging.csproj.nuget.g.targets.
        Restore completed in 6.94 sec for /Users/nordes/programs/RemoteDebugging/RemoteDebugging.csproj.
      
      Restore succeeded.
      
      Mac-Air:RemoteDebugging nordes$ ls
      Controllers			Properties			Startup.cs			appsettings.json		wwwroot
      Program.cs			RemoteDebugging.csproj		appsettings.Development.json	obj
      Mac-Air:RemoteDebugging nordes$ dotnet run
      Using launch settings from /Users/nordes/programs/RemoteDebugging/Properties/launchSettings.json...
      : Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[0]
            User profile is available. Using '/Users/nordes/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
      info: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[58]
            Creating key {cf1e32d1-02af-4459-96ab-635d858c7a62} with creation date 2018-07-28 00:02:46Z, activation date 2018-07-28 00:02:46Z, and expiration date 2018-10-26 00:02:46Z.
      warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
            No XML encryptor configured. Key {cf1e32d1-02af-4459-96ab-635d858c7a62} may be persisted to storage in unencrypted form.
      info: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[39]
            Writing data to file '/Users/nordes/.aspnet/DataProtection-Keys/key-cf1e32d1-02af-4459-96ab-635d858c7a62.xml'.
      Hosting environment: Development
      Content root path: /Users/nordes/programs/RemoteDebugging
      Now listening on: https://localhost:5001
      Now listening on: http://localhost:5000
      Application started. Press Ctrl+C to shut down.
  4. À partir de c:/RemoteDebugging (console), vous pouvez ouvrir Visual studio code en exécutant la commande code . . Si vous n’êtes pas sur Windows, par exemple, sur Mac, simplement ouvrir le répertoire à partir de VS Code.
  5. Une fois dans VS Code, veuillez faire un build (ctrl+shift+p > build et choisir .Net core) directement à partir de l’interface. Ou sinon faire CTRL+Shift+B (Build), normalement vous aurez la création des fichiers .vscode/launch.json et .vscode/tasks.json

En alternative, à partir de Visual Studio 2017, créer un nouveau projet WebApi sous .Net Core 2.1. N’oubliez pas que l’article est pour VS Code en remote débugging. À partir de Visual Studio 2017, il y a tout plein d’article déjà sur ce sujet. L’expérience finale est sensiblement la même, mais moins utile si vous utilisez des ordinateurs distant ou des PC de gens qui n’ont pas de license VS 2017. Dans mon cas, j’ai toujours eu des soucis lors de sessions de debuggage sous VS 2017 en remote debugging.

Création d’un tunnel locale

Le tunnel va vous permettre d’atteindre directement le serveur de destination exécutant votre application. Ceci évitera d’exposer un port SSH de votre conteneur au grand public. Côté sécurité, on préférera toujours un seul point d’entré en évitant d’exposé tous les conteneurs directement au public. Dans une architecture micro-service par exemple, j’aurais un serveur NGinX qui s’occuperait de rediriger mes requêtes vers mes conteneurs sans avoir d’autre point d’accès que l’API ou le Web.

Du coup, afin d’éviter une connection directe sur votre conteneur, vous devrez effectuer un Tunnel SSH. Le tunnel sera important, car pour faire le débuggage, vous devrez vous attacher au processus et ce d’une façon directe. Le tunnel vous permet d’éviter de faire une double connection et vous donne un accès direct au processus en cours d’exécution.

Pour cet article, nous allons nous connecter sur localhost:22222, qui pourrait d’ailleurs utiliser n’importe quel port. Ce sera notre porte d’entrée du tunnel SSH. Ce tunnel comme vous le voyez dans le schéma, effectuera la connection vers la destination finale 10.128.10.123:22 (Conteneur) qui lui même est accessible seulement à partir du serveur Ubuntu (svr-ubuntu:22).

En résumé, et de façon textuel, ça ressemblera à ceci:

  • VS Code => [plink|ssh] => [Tunnel SSH Local (22222) => svr-ubuntu:22 => ip-de-conteneur:22 => attacher au processus de votre application]

Démarrer votre Tunnel

Ouvrez Git Bash (ssh est accessible automatiquement à partir de là, donc créer le tunnel sera juste trop simple)

Commande: ssh -L 22222:10.128.10.123:22 monUtilisateur@svr-ubuntu

Veuillez prendre pour acquis, qu’à l’intérieur du serveur, votre clé SSH est utilisé afin d’atteindre la destination finale.

Utilisation de la clé SSH pour la connection au tunnel (Conteneur)

Cette partie vous permet d’éviter d’inscrire un mot de passe lors de la connection au serveur final (Conteneur). Si vous passez cette étape, vous pourrez bien avoir une erreur de connection lors du débuggage à distance.

  1. Se connecter à votre serveur svr-ubuntu avec “monUtilisateur”
  2. Téléchargez votre clé SSH ~/.ssh/id_rsa (ou extraire ses données en effectuant cat ~/.ssh/id_rsa et sélectionner le résultat de la sortie. Ensuite, copier son contenu dans un fichier texte..
  3. Mettre le fichier dans un endroit quelconque que vous allez ensuite vous rappeler (i.e.: c:/secure_rsa/svr-ubuntu_rsa)
  4. Ouvrir PuttyGen et ensuite charger le fichier (1) c:/secure_rsa/svr-ubuntu_rsa. Par la suite, le logiciel vous demandera de changer le type de fichier à quelque chose de plus récent, à ce moment, acceptez et vous pourrez créer votre fichier “private public key” (PPK). Sauvegardez ce nouveau fichier sous un nom différent (2). Dans mon cas : c:/secure_rsa/myserver.ppk.

Tester votre tunnel SSH

C’est bon, vous pouvez tester votre tunnel SSH. Pour ce faire, veuillez exécuter la commande suivante (À partir de Git Bash):

ssh monUtilisateur@localhost -p 22222 -i c:/secure_rsa/svr-ubuntu_rsa

Si vous le voulez, vous pouvez aussi tester directement avec PLink (putty link). Cet outil permet de faire du ssh directement dans Windows. C’est d’ailleurs ce que nous utiliserons dans notre configuration Visual Studio Code.

Installation de VSDBG

Commande officielle pour installer le vsdbg sur un conteneur lxd:

curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg

Configuration de VS Code (Launch.json)

Plus tôt, vous avez eu a créer le fichier launch.json. Si ce fichier n’existe toujours pas, veuillez le créer (c:/RemoteDebugging/.vscode/launch.json). Ici nous allons utiliser PLink car on peut le faire. Sur Linux, Mac ou bien sous Windows 10 avec le module SSH installé, on pourrait utiliser le tunnel SSH directement de par la commande lancé ici. D’ailleurs je crois que plink permet aussi de lancer le tunnel de par ici directement, donc une des étapes précédente ne serait pas nécessaire.

Ce que nous allons faire ici, c’est simplement ajouter une configuration de build afin de s’attacher à un processus distant. Étant donné que nous ne voulons pas nous attacher directement aux processus du svr-ubuntu, nous allons nous connecter à notre tunnel SSH précédemment configuré.

Voici donc la configuration:

{
    "name": ".NET Core Remote Attach",
    "type": "coreclr",
    "request": "attach",
    "processId": "${command:pickRemoteProcess}",
    "pipeTransport": {
        "pipeProgram": "plink",
        "pipeArgs": [ "-ssh", "-l", "root", "-pw", "monUtilisateur", "-P", "22222", "localhost", "-i", "C:/secure_rsa/svr-ubuntu.ppk" ],
        "debuggerPath": "~/vsdbg/vsdbg",
        "pipeCwd": "${workspaceRoot}",
        "quoteArgs": true
    },
    "sourceFileMap": {
        "/home/ExampleAccount/ExampleProject": "${workspaceRoot}"
    }
}
  • Pour le lien de source, c’est utile si vous avez une machine qui fait du déploiement automatique et que vous voulez faire du remote debugging. Grosso-modo, ça indiquera de faire l’échange du chemin de la machine build vers votre pc local. (La documentation sur cette partie est accessible sur le site officiel d’omnisharp)

Ressources utiles:

Déploiement de l’application

En principe, vous devrez mettre votre application sur le conteneur et la démarrer. Ici on ne traite pas de comment créer un conteneur et du coup, je vous indique comment faire votre build le plus simplement du monde afin de pouvoir l’exécuter sans problème:

dotnet publish RemoteDebugging.csproj -c Debug --self-contained -r ubuntu-x64

En principe, les pdb seront inclus dans votre publication. Si ce n’est pas le cas, veuillez ajouter les “portable library/is portable” dans votre configuration de projet (csproj).

Une fois la publication terminée, vous pouvez prendre les fichiers du répertoire de publication et transférer par FTP dans votre conteneur (quelque chose comme /var/lib/lxd/container/mon-conteneur/rootfs/usr/lib/mon-applicationtest).

Exécution de l’application

Une fois déployés et les permissions ajusté (utilisateur conteneur + changer en mode executable le fichier “RemoteDebugging”) vous pourrez exécuter votre application sur le conteneur LXD. (Exemple d’accès au conteneur à partir de votre Ubuntu: lxc exec mon-conteneur bash). Pour exécuter l’application, seulement aller dans votre répertoire /usr/lib/mon-applicationtest et exécuter la commande ./RemoteDebugging

Le service WebApi devrait être actif en mode console (ça pourrait aussi être exécuter en tant que service avec systemctl. Ceci sera peut-être pour un autre article).

Vous pouvez tester votre service WebApi avec une autre console sur le serveur ubuntu/conteneur qui essaie d’atteindre http://ip-conteneur/api/values . Normalement, vous devriez avoir un peu de json en sortie.

Ok, ça fonctionne! Maintenant la finalité arrive!

S’attacher au processus par Visual Studio Code

Ici, rien de plus simple. Considérant que vous avez configuré votre launch.json, vous pourrez simplement aller dans l’onglet “debug” de visual studio code et sélectionner votre configuration de “.NET Core Remote Attach”, appuyer sur la flèche pour démarrer. Une connection se fera vers votre Tunnel local pour ensuite se connecter au serveur final (avec la clé ppk).

Ajouter un point d’arrêt et debugger en toute tranquillité

Maintenant, ajoutez un point d’arrêt dans votre contrôleur. Une fois fait, ré-exécuter l’appel http de l’étape précédente. Et hop! Magie, votre point d’arrêt est activé.

Bon debugage!

Conclusion

J’espère bien que cet article vous à plus. Merci à l’avance de laisser un commentaire. Il y a moyen de vraiment simplifier la procédure. Ceci n’était qu’une preuve de concept et dans mon cas, étant derrière des serveurs proxy et firewall, à été la seule façon qui à fonctionné. D’ailleurs, pour le service en développement, je n’avais aucun autre moyen de faire, car la dépendance sur des composantes matérielle externe me permettait d’exécuter le code seulement sur la machine aillant toutes les composantes et non sur un pc local.

En gros, les points a noter sont:

  • Utilisation de la clé ssh (id_rsa)
  • Connection par un tunnel
  • Installation du vsdbg sur l’hôte final
  • Exécution/débugage

Enjoy the impossible 😉

]]>
https://blog.honosoft.com/2018/07/29/net-core-et-vscode-debugage-a-distance-conteneur-par-tunnel-ssh/feed/ 0
.Net Core 2.1 ➕ SignalR ➕ JavaScript = ❤️ https://blog.honosoft.com/2018/07/11/net-core-2-1-plus-signalr-plus-javascript-equal-love/?utm_source=rss&utm_medium=rss&utm_campaign=net-core-2-1-plus-signalr-plus-javascript-equal-love https://blog.honosoft.com/2018/07/11/net-core-2-1-plus-signalr-plus-javascript-equal-love/#respond Wed, 11 Jul 2018 13:00:24 +0000 https://blog.honosoft.com/?p=181 Continue Reading]]> Introduction

Comme vous le savez, SignalR à été officiellement ré-écris pour .Net Core 2.1. D’ailleurs, qu’est-ce que SignalR? Bonne question, c’est une façon de communiquer avec un WebSocket, ou de façon dégradé, avec votre backend. Le serveur ici est écrit en .Net, mais si vous voulez avoir un serveur de WebSocket en JavaScript, vous avez d’autres choix (i.e.: Socket.IO + autres alternative). Cependant côté client, à mon avis, les autres librairies ne sont pas aussi bien développé que SignalR. SignalR vous permettra de vous y connecter avec quasi tous les langages connus. Afin de voir comment faire, le repository officiel GitHub de SignalR regorge d’exemples.

Comment marche les WebSocket?

Normalement, la première connection se fait avec un premier appel http(s). Ensuite, le lien se feras en WebSocket (ws ou wss). WS est l’accronyme pour WebSocket et le second S est pour Sécure, tout comme le S dans HTTPS.

But de l’article

Afin de ne pas recopier le tutoriel sur le site officiel, je vais bâtir ce qui suit:

  • Un site web basé sur le modèle WebAPI afin d’éviter à avoir Razor + NPM + etc.
  • Récupérer le JavaScript sans avoir NPM dans le projet
  • Écrire le minimum de JavaScript (SignalR)
  • Écrire le minimum de CSS (Picnic)
  • Écrire le minimum de HTML (Only pure HTML)

Avec pour résultat:

  • Un serveur qui exécutera en arrière plan un lancement d’événements aux 2 secondes sur un processus différent. (Voir BackgroundService)
  • Un hub d’événements que nous appelleront ToastHub.
  • Le front qui vous afficheras les notifications avec 3 niveaux d’alertes aléatoire

Exemple de résultat avec IE et Chrome

Création du projet

À priori, vous savez déjà comment créer le projet ;). Sinon, si vous êtes en ligne de commande, effectuez “dotnet new –help” et vous verrez les types de projets Template que vous avez sur votre pc. Normalement vous y trouverez WebAPI. Du coup dotnet new webapiet n’oubliez pas que le “nom” du projet prendra le nom de votre répertoire (excepté si vous ajoutez l’argument a dotnet new). Dans mon cas, mon projet + espace de nommage sera HoNoSoFt.SignalR.Demo. 😉

Avant de continuer, assurez vous que ça fonctionne. F5 dans Visual Studio, ou dotnet run en ligne de commande. Normalement le site devrait s’ouvrir de lui-même.

Ajout des librairies +dépendances (DI)

Si vous avez la flemme, vous pouvez tout simplement ajouter le package NuGet directement dans le fichier csproj.

<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.2" />

Sinon, si vous êtes plus du type à ouvrir le package manager, veuillez tout simplement ajouter AspNetCore.SignalR (Ici le AspNetCore est très important).

Si ce n’est pas déjà fait. Effectuer un build. Ça vous permettra de vous assurer que le package NuGet est bien arrivé sur votre machine.

Ouvrons le fichier Startup.cs et allons modifier la méthode Configure(…) et ConfigureServices(…). Nous allons ajouter les fichiers par défaut, les fichiers statique, les cookies, les politiques de CORS. Est-ce tout nécessaire? Probablement pas, mais c’est pas plus mal d’avoir ça de configurer.

        public void ConfigureServices(IServiceCollection services)
        {
            // Enregistrement de nos services d'arrière plan (Injection de Dépendence)
            ////services.AddSingleton<IHostedService, ToastEventGenerator>(); 
            // Fin de nos enregistrement de HUBs
            services.Configure(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

            services.AddCors(options => options.AddPolicy("CorsPolicy",
                builder =>
                    {
                        builder.AllowAnyMethod().AllowAnyHeader()
                               .WithOrigins("http://localhost:5000")
                               .AllowCredentials();
                    }));

            services.AddSignalR().AddJsonProtocol(options =>
            {
                options.PayloadSerializerSettings.ContractResolver =
                new CamelCasePropertyNamesContractResolver(); // Utile pour le JavaScript
            });

            services.AddLogging(); // Pour voir un peu plus de logs et pouvoir utiliser ILogger<...>
        }
public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    // Lignes ajoutées
    app.UseDefaultFiles();
    app.UseStaticFiles();
    app.UseCookiePolicy();
    app.UseCors("CorsPolicy");
    app.UseSignalR(routes =>
    {
        routes.MapHub<Hubs.ToastHub>("/hubs/toast"); // <=== Ce sera utile pour la prochaine étape.
    });
    // Fin de l'ajout
    app.UseMvc();
}

Ok, normalement vous devriez avoir une partie encore manquante. Votre implémentation de HUB.

Ici je met une implémentation qui n’utilise pas les authorizations. Du coup, vous ne pourrez pas avoir le nom de la personne qui se connecte. Histoire de garder ça simple et que ça marche partout (même Linux). Je n’ai pas activé l’identification par NTLM.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;

namespace HoNoSoFt.SignalR.Demo.Hubs
{
    //[Authorize]
    public class ToastHub : Hub
    {
        private readonly ILogger<ToastHub> _logger;

        public ToastHub(ILogger<ToastHub> logger)
        {
            _logger = logger;
        }

        public override Task OnConnectedAsync()
        {
            // Pour le Context.user, nous avons besoin de l'attribut Authorize(Authentication).
            _logger.LogInformation("Un utilisateur vient de se joindre.");

            return Task.CompletedTask;
        }

        public override Task OnDisconnectedAsync(Exception exception)
        {
            _logger.LogInformation("Un utilisateur vient de quitter.");

            return Task.CompletedTask;
        }
    }
}
namespace HoNoSoFt.SignalR.Demo.Models
{
    public enum ToastImportance
    {
        Low,
        Medium,
        High
    }
}

 

Désormais, si vous exécuter votre projet. Ça devrait fonctionner (moyennant la correction de votre référence dans votre fichier Startup.cs).

Notez que nous n’avons toujours pas notre service en fond de tâche (BackgroundService).

Réccuppération de la librairie SignalR.js

Oui, c’est simple me direz-vous. Oui, mais non. Si vous êtes dans un projet en NodeJS oui, très simple. De même que, si votre projet vous pensez utiliser NPM/Bower ou Yarn. Dans ce cas-ci, gardons le projet sans références NPM.

Mais alors, comment réccuppérer le JavaScript et le glisser dans wwwroot/js/? C’est simple, ouvrez vous une invite commande, créez-vous un répertoire temporaire.

Exemple:

cd /
mkdir tmp/signalr
cd tmp/signalr
npm init -f 
npm install @aspnet/signalr

REM Vous pouvez ouvrir votre explorateur de fichier afin de copier/coller les fichiers qui seront dans:
REM  > node_modules\@aspnet\signalr\dist\browser\

Fichiers dans le répertoire

  • signalr.js
  • signalr.js.map
  • signalr.min.js
  • signalr.min.js.map

Les fichiers map, si vous ne le savez pas, seront utilisé lors de débuggage. Ça indique où se trouve le code dans les fichiers originaux. Dans notre cas, ce ne sera pas trop utile.

Copier ces fichiers dans votre répertoire “wwwroot/js/”.

Créer votre page html

Ici on va faire simple, nous avons du HTML pure ainsi que l’ajout des informations suivantes:

  • Picnic.css (CDN)
  • site.css (pas encore créé)
  • SignalR comme librairie

Fichier: index.html

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="utf-8" />
    <title>SignalR Demo (Nothing fancy!)</title>
    <link rel="stylesheet" href="https://unpkg.com/picnic">
    <link rel="stylesheet" href="./css/site.css" type="text/css" />
</head>
<body style="zoom: 1;">
    <nav>
        <a href="/" class="brand"><span>HoNoSoFt.SignalR.Demo</span></a>
    </nav>
    <main class="flex center">
        <div>
            <p>Voici une petite démo avec SignalR.</p>
            <p><strong>SignalR Hub:</strong> <i>hubs/toast</i></p>
        </div>

        <div class="third" id="toastContainer">
        </div>
    </main>

    <script src="./js/signalr.js"></script>
    <script>
        var _toastContainer = document.getElementById("toastContainer");

        function addData(sender, importance, message) {
            var span = document.createElement("span");
            span.innerText = "[" + importance + "] " + sender + " : " + message;
            span.className = "button stack " + getClassFromImportance(importance);
            _toastContainer.appendChild(span);
        }

        function getClassFromImportance(importance) {
            switch (importance) {
                case "High": return "error";
                case "Medium": return "warning";
                case "Low": return "success";
            }
        }

        // Doit s'alligner avec ce que vous avez indiqué dans le Startup.cs
        // C'est ici que notre client va se connecter au HUB SignalR backend en WebSocket (ou dégradé)
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/hubs/toast")
            .configureLogging(signalR.LogLevel.Information)
            .build();

        connection.on("SendNotification", function (sender, toastImportance, message) {
            addData(sender, toastImportance, message);
        });

        connection.start().then(function () {
            var p = document.createElement("p");
            p.innerText = "Connection au hub Toast réussi!";
            p.className = "connected";
            _toastContainer.appendChild(p);
        });
    </script>
</body>
</html>

Ajout d’un minimum de style

Comme vous l’avez vu dans la section “head”, il nous manque 1 feuille de style.

La feuille de style est wwwroot/css/site.css:

#toastContainer {
    height: 300px;
    border: 1px #ddd solid;
    overflow: scroll;
}

main {
    padding: 80px 3em 0 3em;
}

.connected {
    color: forestgreen;
    font-weight: bold;
}

Du coup, avant de continuer, vos fichiers devraient ressembler à ce qui suit:

Démarrer votre projet et regardez si au moins votre socket se connecte. (F12 et regarder la console sous chrome)

Ajout du BackgroundService pour générer des événements

Comme vous l’avez vu dans un article précédent, l’implémentation d’une classe qui hérite de BackgroundService implémente automatiquement IHostedService. Du coup, lorsque c’est enregistrer dans le Startup.cs, ça démarre automatiquement au lancement de l’application.

Créons un fichier BackgroundServices/ToastEventGenerator.cs:

using HoNoSoFt.SignalR.Demo.Hubs;
using HoNoSoFt.SignalR.Demo.Models;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace HoNoSoFt.SignalR.Demo.BackgroundServices
{
    public class ToastEventGenerator : BackgroundService
    {
        private readonly IHubContext<ToastHub> _toastHubContext;

        /// <summary>
        /// The random, mais vous devriez utiliser la nouvelle classe secure random.
        /// </summary>
        private static Random _random = new Random((int)DateTime.Now.Ticks & 0x0000FFFF);

        public ToastEventGenerator(IHubContext<ToastHub> toastHubContext)
        {
            _toastHubContext = toastHubContext;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                await BroadcastToastNotificationAsync((ToastImportance)_random.Next(0, 3), "Toast random")
                    .ConfigureAwait(false);

                // Devrait être random
                await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
            }
        }

        private Task BroadcastToastNotificationAsync(ToastImportance toastImportance, string message)
        {
            return _toastHubContext
                .Clients
                .All
                .SendCoreAsync("SendNotification", new[] { "System", toastImportance.ToString(), message });
        }
    }
}

À ce moment-ci, vous avez sans doute dans votre Startup.cs une ligne en commentaire. Celle du BackgroundService, veuillez la réactiver.

// ...
services.AddSingleton<IHostedService, ToastEventGenerator>();
// ...

Structure des fichiers (Final):

Redémarrer votre service et regardez le résultat 😀

Conclusion

Vous voyez, ce n’est pas si compliqué et ce qui est vraiment intéressant, c’est que ça fonctionne partout! Vous pouvez même utiliser un système tel que SignalR afin de faire des notifications en tant que Broker en se connectant sur un Bus de service tel que RabbitMQ, Azure Storage Queue, etc.

Code complet de la démo disponible sur GitHub.

 

]]>
https://blog.honosoft.com/2018/07/11/net-core-2-1-plus-signalr-plus-javascript-equal-love/feed/ 0
.Net Core 2.1 > Nouveauté – BackgroundService (IHostedService) https://blog.honosoft.com/2018/07/03/net-core-2-1-nouveaute-backgroundservice-ihostedservice/?utm_source=rss&utm_medium=rss&utm_campaign=net-core-2-1-nouveaute-backgroundservice-ihostedservice https://blog.honosoft.com/2018/07/03/net-core-2-1-nouveaute-backgroundservice-ihostedservice/#respond Tue, 03 Jul 2018 13:38:39 +0000 https://blog.honosoft.com/?p=153 Continue Reading]]> Peut-être vous en avez entendu parlé, ou peut-être pas. Personnellement, je suis tombé sur l’article MSDN par hasard lors d’une recherche sur un autre sujet. Mais qu’est-ce que ça représente au fait, et qu’est-ce que ça ajoute cette nouveauté qui est proposé avec .Net Core 2.1? C’est simple, ça vous permet de pouvoir créer des services qui s’exécute en arrière plan! (Yahoo!)

Mais pourquoi c’est bon à savoir?

Par exemple, si vous avez une application WebAPI (backend) et que vous voulez effectuer un renouvellement de cache périodique aux 10 minutes, au lieu de lancer une CRON qui appellerait une API sur votre service pour le faire. C’est bien, mais si vous ne pouvez pas avoir de CRON et que vous ne voulez pas exposer un EndPoint de votre API, vous êtes mal barré. Vous devez attendre qu’un utilisateur se connecte à votre site afin de lancer la recharge de cache. Ceci sera lent et pourra vous causer plus de soucis que d’autre chose.

D’autre personne pourrait utiliser ce feature tout simplement pour faire un health check de votre services et de ses dépendances.

Comment ça marche?

C’est simple, vous implémenter la classe abstraite BackgroundService qui elle même hérite des interfaces IHostedService & IDisposable. Du coup vous n’avez qu’à implémenter la partie du déclenchement du service ExecuteAsync(CancellationToken stoppingToken). Notez, que vous pouvez aussi libérer vos ressources lors de la disposition de votre application (I.E. fin d’exécution). Dans ce cas, vous devrez override la méthode StopAsync.

Mais que mettre dans l’exécution asynchrone vous me direz? Dans le cas où vous voulez vérifier aux 10 minutes votre cache, je vous dirais de mettre tout simplement une boucle semi-infinie (utilisation du token d’annulation est là pour ça) qui invalidera et rebâtira au besoin votre cache (Exemple).

Voici un exemple d’implémentation (Classe de BgService):

public class CleanCacheBackgroundService : BackgroundService {
  private readonly ILogger<CleanCacheBackgroundService> _logger;
  private readonly IOtherDependency _otherDependency;

  public CleanCacheBackgroundService(ILogger<CleanCacheBackgroundService> logger, IOtherDependency otherDependency) {
    _logger = logger;
    _otherDependency = otherDependency;
  }

  protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
    _logger.LogDebug($"CleanCacheBackgroundService va démarrer.");
    stoppingToken.Register(() => _logger.LogDebug($"CleanCacheBackgroundService va s'arrêter."));
    while (!stoppingToken.IsCancellationRequested) {
       _logger.LogDebug($"CleanCache : La tâche de nettoyage vient de débuter.");

       otherDependency.CleanCache();

       // Attendre... jusqu'à la nouvelle exécution
       await Task.Delay(otherDependency.DelayBeforeCleaningAgain, stoppingToken).ConfigureAwait(false);
    }
  }

  protected override async Task StopAsync (CancellationToken stoppingToken) {
    // Mettre vos disposition de resource ici. C'est appelé lors de la disposition de votre object.
  }
}

Ensuite, afin que ça démarre au début de votre application, vous n’aurez qu’à enregistrer dans vos dépendances le service d’arrière plan.

// Startup.cs dans la majorité des cas.

public IServiceProvider ConfigureServices(IServiceCollection services)
{            
    // Autre injections (DI)

    // Enregistrement des tâches d'arrière plan.
    services.AddSingleton<IHostedService, CleanCacheBackgroundService>();

    //...
}

Lorsque vous allez démarrer votre application, vos services démarrerons aussi au même moment ;). Ça rest beaucoup plus clean que d’ajouter des démarrage à la mitaine.

CQFD ;P

]]>
https://blog.honosoft.com/2018/07/03/net-core-2-1-nouveaute-backgroundservice-ihostedservice/feed/ 0