Partager via


Créer et exécuter du code .NET à partir de workflows Standard dans Azure Logic Apps

S’applique à : Azure Logic Apps (Standard)

Pour les solutions d’intégration où vous devez créer et exécuter du code .NET à partir de votre workflow d’application logique Standard, vous pouvez utiliser Visual Studio Code avec l’extension Azure Logic Apps (Standard). Cette extension offre les fonctionnalités et avantages suivants :

  • Écrivez votre propre code en créant des fonctions qui ont la flexibilité et le contrôle pour résoudre vos problèmes d’intégration les plus difficiles.
  • Déboguer du code localement dans Visual Studio Code. Parcourez votre code et vos workflows dans la même session de débogage.
  • Déployez du code en même temps que vos workflows. Aucun autre plan de service n’est nécessaire.
  • Prendre en charge les scénarios de migration de BizTalk Server afin de pouvoir « lift-and-shift » les investissements .NET personnalisés d’un emplacement local vers le cloud.

Avec la possibilité d’écrire votre propre code, vous pouvez accomplir des scénarios tels que les suivants :

  • Implémentation personnalisée d’une logique métier
  • Analyse personnalisée pour extraire des informations d’un message entrant
  • Validation des données et transformations simples
  • Mise en forme des messages sortants vers un autre système, par exemple une API
  • Calculs

Cette fonctionnalité n’est pas adaptée aux scénarios suivants :

  • Processus dont l’exécution prend plus de 10 minutes
  • Transformations de messages et de données volumineux
  • Scénarios complexes de traitement par lots et de débats
  • BizTalk Server composants de pipeline qui implémentent la diffusion en continu

Pour plus d’informations sur les limitations dans Azure Logic Apps, consultez Limites et configuration - Azure Logic Apps.

Prérequis

  • Un compte et un abonnement Azure. Si vous n’avez pas encore d’abonnement, vous pouvez vous inscrire pour obtenir un compte Azure gratuitement.

  • La dernière version de Visual Studio Code avec l’extension Azure Logic Apps (Standard). Pour répondre à ces exigences, consultez les conditions préalables pour Créer des flux de travail Standard dans Azure Logic Apps à locataire unique avec Visual Studio Code.

    • La fonctionnalité de fonctions personnalisées est actuellement disponible uniquement dans Visual Studio Code, s’exécutant sur un système d’exploitation Windows.

    • La fonctionnalité de fonctions personnalisées prend actuellement en charge l’appel de .NET Framework et .NET 8 pour les workflows d’application logique hébergés par Azure.

  • Dossier local à utiliser pour créer votre projet de code

Limites

La création de fonctions personnalisées n’est actuellement pas disponible dans le portail Azure. Toutefois, après avoir déployé vos fonctions à partir de Visual Studio Code sur Azure, suivez les étapes décrites dans Appeler votre code à partir d’un flux de travail pour le portail Azure. Vous pouvez utiliser l’action intégrée nommée Appeler une fonction locale dans cette application logique pour sélectionner parmi vos fonctions personnalisées déployées et exécuter votre code. Les actions suivantes dans votre flux de travail peuvent référencer les sorties de ces fonctions, comme dans n’importe quel autre flux de travail. Vous pouvez afficher l’historique d’exécution, les entrées et les sorties de l’action intégrée.

Créer le projet de code

La dernière extension Azure Logic Apps (Standard) pour Visual Studio Code comprend un modèle de projet de code qui fournit une expérience rationalisée pour l’écriture, le débogage et le déploiement de votre propre code avec vos workflows. Ce modèle de projet crée un fichier d’espace de travail et deux exemples de projets : un projet pour écrire votre code, l’autre pour créer vos workflows.

Remarque

Vous ne pouvez pas utiliser le même dossier de projet à la fois pour votre code et vos workflows.

  1. Ouvrez Visual Studio Code. Sélectionnez l’icône Azure dans la barre d’activité. (Clavier : Maj+Alt+A)

  2. Dans la fenêtre Azure qui s’ouvre, dans la barre d’outils de la section Espace de travail, à partir du menu Azure Logic Apps, sélectionnez Créer un espace de travail d’application logique.

    Capture d’écran montrant Visual Studio Code, la fenêtre Azure, la barre d’outils de la section Espace de travail et l’option sélectionnée pour Créer un espace de travail d’application logique.

  3. Dans la zone Sélectionner un dossier, recherchez et sélectionnez le dossier local que vous avez créé pour votre projet.

  4. Lorsque la zone d’invite Créer un nouvel espace de travail d’application logique s’affiche, indiquez un nom pour votre espace de travail :

    Capture d’écran montrant Visual Studio Code avec une invite pour entrer le nom d’espace de travail.

    Cet exemple continue avec MyLogicAppWorkspace.

  5. Lorsque la zone d’invite Sélectionner un modèle de projet pour votre espace de travail d’application logique s’affiche, sélectionnez Application logique avec un projet de code personnalisé.

    Capture d’écran montrant Visual Studio Code avec une invite pour sélectionner un modèle de projet pour l’espace de travail d’application logique.

  6. Pour les workflows d’application logique Standard hébergés par Azure, suivez l’invite pour sélectionner .NET Framework ou .NET 8.

  7. Suivez les invites suivantes pour fournir les valeurs d’exemple suivantes :

    Article Valeur d'exemple
    Nom de fonction pour votre projet de fonctions .NET WeatherForecast
    Nom d’espace de noms pour votre projet de fonctions .NET Contoso.Enterprise
    Modèle de workflow :
    - Workflow avec état
    - Workflow sans état
    Workflow avec état
    Nom du flux de travail MyWorkflow
  8. Sélectionnez Ouvrir dans la fenêtre actuelle.

    Une fois cette étape terminée, Visual Studio Code crée votre espace de travail, qui comprend par défaut un projet de fonctions .NET et un projet d’application logique. Par exemple :

    Capture d’écran montrant Visual Studio Code avec l’espace de travail créé.

    Nœud Description
    <workspace-name> Contient à la fois votre projet de fonctions .NET et votre projet de workflow d’application logique.
    Fonctions Contient les artefacts de votre projet de fonctions .NET. Par exemple, le fichier <function-name>.cs est le fichier de code dans lequel vous pouvez créer votre code.
    Application logique Contient les artefacts de votre projet d’application logique, y compris un flux de travail vide.

Écrire le code

  1. Dans votre espace de travail, développez le nœud Fonctions , s’il n’est pas déjà développé.

  2. Ouvrez le fichier <function-name>.cs, nommé WeatherForecast.cs dans cet exemple.

    Par défaut, ce fichier contient un exemple de code qui contient les éléments de code suivants, ainsi que les exemples de valeurs précédemment fournis, le cas échéant :

    • Nom de l’espace de noms
    • Nom de classe
    • Nom de la fonction
    • Paramètres de fonction
    • Type de retour
    • Type complexe

    L’exemple suivant montre l’exemple de code complet :

    //------------------------------------------------------------
    // Copyright (c) Microsoft Corporation. All rights reserved.
    //------------------------------------------------------------
    
    namespace Contoso.Enterprise
    {
        using System;
        using System.Collections.Generic;
        using System.Threading.Tasks;
        using Microsoft.Azure.Functions.Extensions.Workflows;
        using Microsoft.Azure.WebJobs;
        using Microsoft.Extensions.Logging;
    
        /// <summary>
        /// Represents the WeatherForecast flow invoked function.
        /// </summary>
        public class WeatherForecast
        {
    
            private readonly ILogger<WeatherForecast> logger;
    
            public WeatherForecast(ILoggerFactory loggerFactory)
            {
                logger = loggerFactory.CreateLogger<WeatherForecast>();
            }
    
            /// <summary>
            /// Executes the logic app workflow.
            /// </summary>
            /// <param name="zipCode">The zip code.</param>
            /// <param name="temperatureScale">The temperature scale (e.g., Celsius or Fahrenheit).</param>
            [FunctionName("WeatherForecast")]
            public Task<Weather> Run([WorkflowActionTrigger] int zipCode, string temperatureScale)
            {
    
                this.logger.LogInformation("Starting WeatherForecast with Zip Code: " + zipCode + " and Scale: " + temperatureScale);
    
                // Generate random temperature within a range based on the temperature scale
                Random rnd = new Random();
                var currentTemp = temperatureScale == "Celsius" ? rnd.Next(1, 30) : rnd.Next(40, 90);
                var lowTemp = currentTemp - 10;
                var highTemp = currentTemp + 10;
    
                // Create a Weather object with the temperature information
                var weather = new Weather()
                {
                    ZipCode = zipCode,
                    CurrentWeather = $"The current weather is {currentTemp} {temperatureScale}",
                    DayLow = $"The low for the day is {lowTemp} {temperatureScale}",
                    DayHigh = $"The high for the day is {highTemp} {temperatureScale}"
                };
    
                return Task.FromResult(weather);
            }
    
            /// <summary>
            /// Represents the weather information for WeatherForecast.
            /// </summary>
            public class Weather
            {
                /// <summary>
                /// Gets or sets the zip code.
                /// </summary>
                public int ZipCode { get; set; }
    
                /// <summary>
                /// Gets or sets the current weather.
                /// </summary>
                public string CurrentWeather { get; set; }
    
                /// <summary>
                /// Gets or sets the low temperature for the day.
                /// </summary>
                public string DayLow { get; set; }
    
                /// <summary>
                /// Gets or sets the high temperature for the day.
                /// </summary>
                public string DayHigh { get; set; }
            }
        }
    }
    

    La définition de fonction inclut une méthode Run par défaut que vous pouvez utiliser pour commencer. Cet exemple de méthode Run illustre certaines des fonctionnalités disponibles avec la fonctionnalité de fonctions personnalisées, telles que la transmission de différentes entrées et sorties, y compris les types .NET complexes.

    Le fichier <function-name>.cs inclut également l’interface ILogger, qui fournit la prise en charge de la journalisation des événements dans une ressource Application Insights. Vous pouvez envoyer des informations de suivi à Application Insights et stocker ces informations aux côtés des informations de trace de vos flux de travail, par exemple :

    private readonly ILogger<WeatherForecast> logger;
    
    public WeatherForecast(ILoggerFactory loggerFactory)
    {
        logger = loggerFactory.CreateLogger<WeatherForecast>();
    }
    
    [FunctionName("WeatherForecast")]
    public Task<Weather> Run([WorkflowActionTrigger] int zipCode, string temperatureScale)
    {
    
        this.logger.LogInformation("Starting WeatherForecast with Zip Code: " + zipCode + " and Scale: " + temperatureScale);
    
        <...>
    
    }
    
  3. Remplacez l’exemple de code de fonction par votre propre et modifiez la méthode Run par défaut pour vos propres scénarios. Vous pouvez également copier la fonction, y compris la déclaration [FunctionName("<*function-name*>")], puis renommer la fonction avec un nom unique. Vous pouvez ensuite modifier la fonction renommée pour s’adapter à vos besoins.

Cet exemple continue avec l’exemple de code sans aucune modification.

Compiler et générer votre code

Une fois que vous avez terminé d’écrire votre code, compilez pour vous assurer qu’aucune erreur de build n’existe. Votre projet de fonctions .NET inclut automatiquement des tâches de génération, qui compilent puis ajoutent votre code au dossier lib\custom de votre projet d’application logique où les workflows recherchent les fonctions personnalisées à exécuter. Ces tâches placent les assemblys dans le dossier lib\custom\net472 ou lib\custom\net8 en fonction de votre version de .NET.

  1. Dans Visual Studio Code, sélectionnez le menu Terminal, puis sélectionnez Nouveau terminal.

  2. Dans la liste des répertoires de travail qui s’affiche, sélectionnez Fonctions comme répertoire de travail actuel pour le nouveau terminal.

    Capture d’écran montrant Visual Studio Code, une invite pour le répertoire de travail actuel, et le répertoire Fonctions sélectionné.

    Visual Studio Code ouvre une fenêtre de terminal avec une invite de commandes.

  3. Dans la fenêtre Terminal , à l’invite de commandes, entrez dotnet restore.

    Visual Studio Code analyse vos projets et détermine s’ils sont à jour.

    Capture d’écran montrant Visual Studio Code, la fenêtre Terminal, et la commande dotnet restore complétée.

  4. Une fois l’invite de commandes réapparaît, entrez dotnet build. Ou, dans le menu Terminal, sélectionnez Exécuter la tâche. Dans la liste des tâches, sélectionnez générer (Fonctions).

    Si votre build réussit, la fenêtre Terminal signale que la génération a réussi.

  5. Vérifiez que les éléments suivants existent dans votre projet d’application logique :

    • Dans votre espace de travail, développez les dossiers LogicApp>lib\custom>net472 ou net8, en fonction de votre version de .NET. Vérifiez que le sous-dossier nommé net472 ou net8 contient les fichiers d’assembly (DLL) nécessaires pour exécuter votre code, y compris un fichier nommé <nom-fonction>.dll.

    • Dans votre espace de travail, développez les dossiers suivants : LogicApp>lib\custom><function-name>. Vérifiez que le sous-dossier nommé <function-name> contient un fichier function.json, qui inclut les métadonnées relatives au code de fonction que vous avez écrit. Le concepteur de flux de travail utilise ce fichier pour déterminer les entrées et sorties nécessaires lors de l’appel de votre code.

    L’exemple suivant montre des exemples d’assemblys et d’autres fichiers générés dans le projet d’application logique :

    Capture d’écran montrant Visual Studio Code et l’espace de travail d’application logique avec un projet de fonctions.NET et un projet d’application logique, désormais avec les assemblys générés et les autres fichiers nécessaires.

Apellez votre code depuis un flux de travail

Après avoir confirmé que votre code est compilé et que votre projet d’application logique contient les fichiers nécessaires à l’exécution de votre code, ouvrez le workflow par défaut inclus dans votre projet d’application logique.

  1. Dans votre espace de travail, sous LogicApp, développez le nœud <workflow-name>, ouvrez le menu contextuel pour workflow.json, puis sélectionnez Ouvrir Designer.

    Dans le concepteur de flux de travail qui s’ouvre, le workflow par défaut, inclus dans votre projet d’application logique, s’affiche avec le déclencheur et les actions suivants :

  2. Sélectionnez l’action nommée Appeler une fonction locale dans cette application logique.

    Le volet d’informations de l’action s’ouvre à droite.

    Capture d’écran montrant Visual Studio Code, le concepteur de flux de travail, et le flux de travail par défaut avec déclencheur et actions.

  3. Vérifiez et vérifiez que la valeur du paramètre Nom de la fonction est définie sur la fonction que vous souhaitez exécuter. Passez en revue ou modifiez toutes les autres valeurs de paramètre que votre fonction utilise.

Déboguer votre code et votre workflow

  1. Répétez les étapes suivantes pour démarrer l’émulateur de stockage Azurite trois fois : une fois chacune pour les services de stockage Azure suivants :

    • Service Blob Azure
    • Azure Queue Service
    • Service Table Azure
    1. Dans Visual Studio Code, dans le menu Affichage, sélectionnez Palette de commandes.

    2. À l’invite qui s’affiche, recherchez et sélectionnez Azurite : Démarrer le service Blob.

    3. Dans la liste des répertoires de travail qui s’affiche, sélectionnez LogicApp.

    4. Répétez ces étapes pour Azurite : Démarrer le service file d’attente et Azurite : Démarrer le service table.

    Vous réussissez lorsque la barre des tâches de Visual Studio Code en bas de l’écran montre les trois services de stockage en cours d’exécution, par exemple :

    Capture d’écran montrant la barre des tâches Visual Studio Code avec Azure Blob Service, Azure Queue Service, er Azure Table Service en cours d’exécution.

  2. Effectuez les étapes suivantes pour attacher le débogueur à votre projet d’application logique :

    1. Dans la barre d’activités de Visual Studio Code, sélectionnez Exécuter et déboguer. (Clavier : Ctrl+Maj+D)

      Capture d’écran montrant la barre d’activités de Visual Studio Code et l’icône Exécuter et déboguer sélectionnée.

    2. Dans la liste Exécuter et déboguer, sélectionnez Attacher à une application logique (LogicApp) si ce n’est pas déjà sélectionné, puis sélectionnez Lire (flèche verte).

      Capture d’écran montrant la liste Exécuter et déboguer avec Attacher à une application logique sélectionné et le bouton Lire sélectionné.

      La fenêtre Terminal s’ouvre et affiche le processus de débogage démarré. La fenêtre Console de débogage s’affiche et affiche les états de débogage. En bas de Visual Studio Code, la barre des tâches devient orange, ce qui indique que le débogueur .NET est chargé.

  3. Effectuez les étapes suivantes, en fonction de votre code, pour attacher le débogueur à votre projet de fonctions .NET :

    Projets .NET 8

    1. Dans Visual Studio Code, dans le menu Affichage, sélectionnez Palette de commandes.

    2. Dans la palette de commandes, recherchez et sélectionnez Déboguer : attacher à un processus .NET 5+ ou .NET Core.

      Capture d’écran montrant la liste Exécuter et déboguer avec Attacher à des fonctions .NET sélectionné et le bouton Lire sélectionné.

    3. Dans la liste, recherchez et sélectionnez le processus dotnet.exe. Si plusieurs processus dotnet.exe sont répertoriés, sélectionnez le processus avec le chemin d’accès suivant :

      <nom-lecteur>:\Users<nom-utilisateur>.azure-functions-core-tools\Functions\ExtensionBundles\Microsoft.Azure.Functions.ExtensionBundle.Workflows<version-pack-extension>\CustomCodeNetFxWorker\net8\Microsoft.Azure.Workflows.Functions.CustomCodeNetFxWorker.dll

    Projets .NET Framework

    Dans la liste Exécuter et déboguer, sélectionnez Attacher à des fonctions .NET (Functions) si ce n’est pas déjà fait, puis sélectionnez Lire (flèche verte).

    Capture d’écran montrant la liste Exécuter et déboguer avec l’option Attacher à des fonctions .NET (Functions) et le bouton Lire sélectionnés.

  4. Pour définir des points d’arrêt, dans votre définition de fonction (<function-name>.cs) ou votre définition de workflow (workflow.json), recherchez le numéro de ligne où vous souhaitez le point d’arrêt, puis sélectionnez la colonne à gauche, par exemple :

    Capture d’écran montrant Visual Studio Code et le fichier de code de l’option Open avec un point d'arrêt défini pour une ligne de code.

  5. Pour exécuter manuellement le déclencheur de demande dans votre flux de travail, ouvrez la page Vue d’ensemble du flux de travail.

    1. Dans votre projet d’application logique, ouvrez le menu contextuel du fichier workflow.json, puis sélectionnez Vue d’ensemble.

      Dans la page Vue d’ensemble du flux de travail, le bouton Exécuter le déclencheur est disponible lorsque vous souhaitez démarrer manuellement le flux de travail. Sous Propriétés du flux de travail, la valeur URL de rappel correspond à l’URL d’un point de terminaison pouvant être appelé créé par le déclencheur De demande dans votre flux de travail. Vous pouvez envoyer des requêtes à cette URL pour déclencher votre flux de travail à partir d’autres applications, y compris d’autres workflows d’application logique.

      Capture d’écran montrant Visual Studio Code et la page Vue d’ensemble du flux de travail ouverte.

  6. Dans la barre d’outils du volet Vue d’ensemble, sélectionnez Exécuter le déclencheur.

    Une fois que votre workflow a commencé à s’exécuter, le débogueur active votre premier point d’arrêt.

  7. Dans le menu Exécuter ou la barre d’outils du débogueur, sélectionnez une action de débogage.

    Une fois l’exécution du flux de travail terminée, la page Vue d’ensemble affiche l’exécution terminée et les détails de base sur cette exécution.

  8. Pour passer en revue plus d’informations sur l’exécution du flux de travail, sélectionnez l’exécution terminée. Ou, dans la liste en regard de la colonne Durée, sélectionnez Afficher l’exécution.

    Capture d’écran montrant Visual Studio Code et une exécution de flux de travail terminée.

Déploiement de votre code

Vous pouvez déployer vos fonctions personnalisées de la même façon que vous déployez votre projet d’application logique. Que vous déployiez à partir de Visual Studio Code ou utilisiez un processus DevOps CI/CD, veillez à générer votre code et vérifiez que tous les assemblys dépendants existent dans le dossier du projet d’application logique avant de procéder au déploiement :

  • .NET 4.7.2 : dossier lib/custom/net472

  • .NET 8 : dossier lib/custom/net8

Pour plus d’informations, consultez Déployer des workflows Standard à partir de Visual Studio Code vers Azure.

Résoudre les problèmes

Erreur du volet Informations sur l’action

Dans le concepteur de flux de travail, lorsque vous sélectionnez l’action intégrée nommée Appeler une fonction locale dans cette application logique, le volet d’informations de l’action affiche le message suivant :

Failed to retrieve dynamic inputs. Error details:

Dans ce scénario, examinez votre projet d’application logique pour case activée si le dossier LogicApp\lib\custom est vide. S’il est vide, dans le menu Terminal, sélectionnez Exécuter les tâches>Fonctions de génération.

Aucun processus portant le nom spécifié n’est en cours d’exécution

Si vous recevez ce message d’erreur lorsque vous exécutez votre flux de travail, le processus de débogueur est probablement attaché à .NET Functions, plutôt qu’à votre application logique.

Pour résoudre ce problème, dans la liste Exécuter et déboguer, sélectionnez Attacher à une application logique (LogicApp), puis sélectionnez Lire (flèche verte).

Package non importé correctement

Si la fenêtre Sortie affiche une erreur similaire au message suivant, vérifiez que .NET 6.0 au minimum est installé. Si vous avez installé cette version, essayez de désinstaller, puis de réinstaller.

C:\Users\yourUserName\.nuget\packages\microsoft.net.sdk.functions\4.2.0\build\Microsoft.NET.Sdk.Functions.targets(83,5): warning : The ExtensionsMetadataGenerator package was not imported correctly. Are you missing 'C:\Users\yourUserName\.nuget\packages\microsoft.azure.webjobs.script.extensionsmetadatagenerator\4.0.1\build\Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator.targets' or 'C:\Users\yourUserName\.nuget\packages\microsoft.azure.webjobs.script.extensionsmetadatagenerator\4.0.1\build\Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator.props'? [C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\WeatherForecast.csproj] WeatherForecast -> C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\\bin\Debug\net472\WeatherForecast.dll C:\Users\yourUserName\.nuget\packages\microsoft.net.sdk.functions\4.2.0\build\Microsoft.NET.Sdk.Functions.Build.targets(32,5): error : It was not possible to find any compatible framework version [C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\WeatherForecast.csproj] C:\Users\yourUserName\.nuget\packages\microsoft.net.sdk.functions\4.2.0\build\Microsoft.NET.Sdk.Functions.Build.targets(32,5): error : The specified framework 'Microsoft.NETCore.App', version '6.0.0' was not found. [C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\WeatherForecast.csproj] C:\Users\yourUserName\.nuget\packages\microsoft.net.sdk.functions\4.2.0\build\Microsoft.NET.Sdk.Functions.Build.targets(32,5): error : - Check application dependencies and target a framework version installed at: [C:\Desktop\...\custom-code-project\MyLogicAppWorkspace\Function\WeatherForecast.csproj]

Les builds échouent

Si votre fonction n’inclut pas de variables et que vous générez votre code, la fenêtre Sortie peut afficher les messages d’erreur suivants :

C:\Users\yourUserName\...\custom-code-project\Function\func.cs (24,64): error CS1031: Type expected [C:\Users\yourUserName\...\custom-code-project\Function\func.csproj]
C:\Users\yourUserName\...\custom-code-project\Function\func.cs (24,64): error CS1001: Identifier expected [C:\Users\yourUserName\...\custom-code-project\Function\func.csproj]

Build FAILED.

C:\Users\yourUserName\...\custom-code-project\Function\func.cs (24,64): error CS1031: Type expected [C:\Users\yourUserName\...\custom-code-project\Function\func.csproj]
C:\Users\yourUserName\...\custom-code-project\Function\func.cs (24,64): error CS1001: Identifier expected [C:\Users\yourUserName\...\custom-code-project\Function\func.csproj]

0 Warning(s)
2 Error(s)

Pour résoudre ce problème, dans la méthode Run de votre code, ajoutez le paramètre suivant :

string parameter1 = null

L’exemple suivant montre comment la signature de méthode Run apparaît :

public static Task<Weather> Run([WorkflowActionTrigger] int zipCode, string temperatureScale, string parameter1 = null)

Étapes suivantes

Créer des workflows Standard avec Visual Studio Code