Use Visual Studio 2022 to develop and debug modules for Azure IoT Edge

Applies to: IoT Edge 1.4 checkmark IoT Edge 1.4

Important

IoT Edge 1.4 is the supported release. If you are on an earlier release, see Update IoT Edge.

This article shows you how to use Visual Studio 2022 to develop, debug, and deploy custom Azure IoT Edge modules. Visual Studio 2022 provides templates for IoT Edge modules written in C and C#. The supported device architectures are Windows x64, Linux x64, ARM32, and ARM64 (preview). For more information about supported operating systems, languages, and architectures, see Language and architecture support.

This article includes steps for two IoT Edge development tools.

  • Command line interface (CLI) is the preferred tool for development.
  • Azure IoT Edge tools for Visual Studio extension. The extension is in maintenance mode.

Use the tool selector button at the beginning to choose your tool option for this article. Both tools provide the following benefits:

  • Create, edit, build, run, and debug IoT Edge solutions and modules on your local development computer.
  • Code your Azure IoT modules in C or C# with the benefits of Visual Studio development.
  • Deploy your IoT Edge solution to an IoT Edge device via Azure IoT Hub.

Prerequisites

This article assumes that you use a machine running Windows as your development machine.

  • Install or modify Visual Studio 2022 on your development machine. Choose the Azure development and Desktop development with C++ workloads options.

  • Download and install Azure IoT Edge Tools from the Visual Studio Marketplace. You can use the Azure IoT Edge Tools extension to create and build your IoT Edge solution. The preferred development tool is the command-line (CLI) Azure IoT Edge Dev Tool. The extension includes the Azure IoT Edge project templates used create the Visual Studio project. Currently, you need the extension installed regardless of the development tool you use.

    Important

    The Azure IoT Edge Tools for VS 2022 extension is in maintenance mode. The preferred development tool is the command-line (CLI) Azure IoT Edge Dev Tool.

    Tip

    If you are using Visual Studio 2019, download and install Azure IoT Edge Tools for VS 2019 from the Visual Studio marketplace.

  • Install the Vcpkg library manager

    git clone https://github.com/Microsoft/vcpkg
    cd vcpkg
    bootstrap-vcpkg.bat
    

    Install the azure-iot-sdk-c package for Windows

    vcpkg.exe install azure-iot-sdk-c:x64-windows
    vcpkg.exe --triplet x64-windows integrate install
    
  • Download and install a Docker compatible container management system on your development machine to build and run your module images. For example, install Docker Community Edition.

  • To develop modules with Linux containers, use a Windows computer that meets the requirements for Docker Desktop.

  • Create an Azure Container Registry or Docker Hub to store your module images.

    Tip

    You can use a local Docker registry for prototype and testing purposes instead of a cloud registry.

  • Install the Azure CLI.

  • To test your module on a device, you need an active IoT Hub with at least one IoT Edge device. To create an IoT Edge device for testing, you can create one in the Azure portal or with the CLI:

    • Creating one in the Azure portal is the quickest. From the Azure portal, go to your IoT Hub resource. Select Devices under the Device management menu and then select Add Device.

      In Create a device,name your device using Device ID, check IoT Edge Device, then select Save in the lower left.

      Finally, confirm that your new device exists in your IoT Hub, from the Device management > Devices menu. For more information on creating an IoT Edge device through the Azure portal, read Create and provision an IoT Edge device on Linux using symmetric keys.

    • To create an IoT Edge device with the CLI, follow the steps in the quickstart for Linux or Windows. In the process of registering an IoT Edge device, you create an IoT Edge device.

    If you're running the IoT Edge daemon on your development machine, you might need to stop EdgeHub and EdgeAgent before you start development in Visual Studio.

Create an Azure IoT Edge project

The IoT Edge project template in Visual Studio creates a solution to deploy to IoT Edge devices. First, you create an Azure IoT Edge solution. Then, you create a module in that solution. Each IoT Edge solution can contain more than one module.

In our solution, we're going to build three projects. The main module that contains EdgeAgent and EdgeHub, in addition to the temperature sensor module. Next, you add two more IoT Edge modules.

Important

The IoT Edge project structure created by Visual Studio isn't the same as the one in Visual Studio Code.

Currently, the Azure IoT Edge Dev Tool CLI doesn't support creating the Visual Studio project type. You need to use the Visual Studio IoT Edge extension to create the Visual Studio project.

  1. In Visual Studio, create a new project.

  2. In Create a new project, search for Azure IoT Edge. Select the project that matches the platform and architecture for your IoT Edge device, and select Next.

    Create New Project

  3. In Configure your new project, enter a name for your project, specify the location, and select Create.

  4. In Add Module, select the type of module you want to develop. If you have an existing module you want to add to your deployment, select Existing module.

  5. In Module Name, enter a name for your module. Choose a name that's unique within your container registry.

  6. In Repository Url, provide the name of the module's image repository. Visual Studio autopopulates the module name with localhost:5000/<your module name>. Replace it with your own registry information. Use localhost if you use a local Docker registry for testing. If you use Azure Container Registry, then use the login server from your registry's settings. The login server looks like <registry name>.azurecr.io. Only replace the localhost:5000 part of the string so that the final result looks like <registry name>.azurecr.io/<your module name>.

  7. Select Add to add your module to the project.

    Screenshot of how to add Application and Module.

    Note

    If you have an existing IoT Edge project, you can change the repository URL by opening the module.json file. The repository URL is located in the repository property of the JSON file.

Now, you have an IoT Edge project and an IoT Edge module in your Visual Studio solution.

Project structure

In your solution, there are two project level folders including a main project folder and a single module folder. For example, you may have a main project folder named AzureIotEdgeApp1 and a module folder named IotEdgeModule1. The main project folder contains your deployment manifest.

The module project folder contains a file for your module code named either Program.cs or main.c depending on the language you chose. This folder also contains a file named module.json that describes the metadata of your module. Various Docker files included here provide the information needed to build your module as a Windows or Linux container.

Deployment manifest of your project

The deployment manifest you edit is named deployment.debug.template.json. This file is a template of an IoT Edge deployment manifest that defines all the modules that run on a device along with how they communicate with each other. For more information about deployment manifests, see Learn how to deploy modules and establish routes.

If you open this deployment template, you see that the two runtime modules, edgeAgent and edgeHub are included, along with the custom module that you created in this Visual Studio project. A fourth module named SimulatedTemperatureSensor is also included. This default module generates simulated data that you can use to test your modules, or delete if it's not necessary. To see how the simulated temperature sensor works, view the SimulatedTemperatureSensor.csproj source code.

Set IoT Edge runtime version

Currently, the latest stable runtime version is 1.4. You should update the IoT Edge runtime version to the latest stable release or the version you want to target for your devices.

  1. In the Solution Explorer, right-click the name of your main project and select Set IoT Edge runtime version.

    Screenshot of how to find and select the menu item named 'Set IoT Edge Runtime version'.

  2. Use the drop-down menu to choose the runtime version that your IoT Edge devices are running, then select OK to save your changes. If no change was made, select Cancel to exit.

    Currently, the extension doesn't include a selection for the latest runtime versions. If you want to set the runtime version higher than 1.2, open deployment.debug.template.json deployment manifest file. Change the runtime version for the system runtime module images edgeAgent and edgeHub. For example, if you want to use the IoT Edge runtime version 1.4, change the following lines in the deployment manifest file:

    "systemModules": {
       "edgeAgent": {
        //...
          "image": "mcr.microsoft.com/azureiotedge-agent:1.4"
        //...
       "edgeHub": {
       //...
          "image": "mcr.microsoft.com/azureiotedge-hub:1.4",
       //...
    
  3. If you changed the version, regenerate your deployment manifest by right-clicking the name of your project and select Generate deployment for IoT Edge. This generates a deployment manifest based on your deployment template and appears in the config folder of your Visual Studio project.

  1. Open deployment.debug.template.json deployment manifest file. The deployment manifest is a JSON document that describes the modules to be configured on the targeted IoT Edge device.

  2. Change the runtime version for the system runtime module images edgeAgent and edgeHub. For example, if you want to use the IoT Edge runtime version 1.4, change the following lines in the deployment manifest file:

    "systemModules": {
        "edgeAgent": {
        //...
            "image": "mcr.microsoft.com/azureiotedge-agent:1.4",
        //...
        "edgeHub": {
        //...
            "image": "mcr.microsoft.com/azureiotedge-hub:1.4",
        //...
    

Module infrastructure & development options

When you add a new module, it comes with default code that is ready to be built and deployed to a device so that you can start testing without touching any code. The module code is located within the module folder in a file named Program.cs (for C#) or main.c (for C).

The default solution is built so that the simulated data from the SimulatedTemperatureSensor module is routed to your module, which takes the input and then sends it to IoT Hub.

When you're ready to customize the module template with your own code, use the Azure IoT Hub SDKs to build modules that address the key needs for IoT solutions such as security, device management, and reliability.

Debug using the simulator

The Azure IoT EdgeHub Dev Tool provides a local development and debug experience. The tool helps start IoT Edge modules without the IoT Edge runtime so that you can create, develop, test, run, and debug IoT Edge modules and solutions locally. You don't have to push images to a container registry and deploy them to a device for testing.

For more information, see Azure IoT EdgeHub Dev Tool.

To initialize the tool in Visual Studio:

  1. Retrieve the connection string of your IoT Edge device (found in your IoT Hub) from the Azure portal or from the Azure CLI.

    If using the CLI to retrieve your connection string, use this command, replacing "[device_id]" and "[hub_name]" with your own values:

    az iot hub device-identity connection-string show --device-id [device_id] --hub-name [hub_name]
    
  2. From the Tools menu in Visual Studio, select Azure IoT Edge Tools > Setup IoT Edge Simulator.

  3. Paste the connection string and select OK.

Note

You need to follow these steps only once on your development computer as the results are automatically applied to all subsequent Azure IoT Edge solutions. This procedure can be followed again if you need to change to a different connection string.

Build and debug a single module

Typically, you want to test and debug each module before running it within an entire solution with multiple modules. The IoT Edge simulator tool allows you to run a single module in isolation a send messages over port 53000.

  1. In Solution Explorer, select and highlight the module project folder (for example, IotEdgeModule1). Set the custom module as the startup project. Select Project > Set as StartUp Project from the menu.

  2. Press F5 or select the run toolbar button to start the IoT Edge simulator for a single module. It may take 10 to 20 seconds the initially.

    Screenshot of how to run a module.

  3. You should see a .NET Core console app window appear if the module has been initialized successfully.

  4. Set a breakpoint to inspect the module.

    • If developing in C#, set a breakpoint in the PipeMessage() function in ModuleBackgroundService.cs.
    • If using C, set a breakpoint in the InputQueue1Callback() function in main.c.
  5. Test the module by sending a message. When you're debugging a single module, the simulator listens on the default port 53000 for messages. To send a message to your module, run the following curl command from a command shell like Git Bash or WSL Bash.

    curl --header "Content-Type: application/json" --request POST --data '{"inputName": "input1","data":"hello world"}' http://localhost:53000/api/v1/messages
    

    If you get the error unmatched close brace/bracket in URL, try the following command instead:

    curl --header "Content-Type: application/json" --request POST --data "{\"inputName\": \"input1\", \"data\", \"hello world\"}"  http://localhost:53000/api/v1/messages
    

    Screenshot of the output console, Visual Studio project, and Bash window.

    The breakpoint should be triggered. You can watch variables in the Visual Studio Locals window, found when the debugger is running. Go to Debug > Windows > Locals.

    In your Bash or shell, you should see a {"message":"accepted"} confirmation.

    In your .NET console you should see:

    IoT Hub module client initialized.
    Received message: 1, Body: [hello world]
    

    Tip

    You can also use PostMan or other API tools to send messages instead of curl.

  6. Press Ctrl + F5 or select the stop button to stop debugging.

Build and debug multiple modules

After you're done developing a single module, you might want to run and debug an entire solution with multiple modules. The IoT Edge simulator tool allows you to run all modules defined in the deployment manifest including a simulated edgeHub for message routing. In this example, you run two custom modules and the simulated temperature sensor module. Messages from the simulated temperature sensor module are routed to each custom module.

  1. In Solution Explorer, add a second module to the solution by right-clicking the main project folder. On the menu, select Add > New IoT Edge Module.

    Screenshot of how to add a 'New IoT Edge Module' from the menu.

  2. In the Add module window give your new module a name and replace the localhost:5000 portion of the repository URL with your Azure Container Registry login server, like you did before.

  3. Open the file deployment.debug.template.json to see that the new module has been added in the modules section. A new route was also added to the routes section in EdgeHub to send messages from the new module to IoT Hub. To send data from the simulated temperature sensor to the new module, add another route with the following line of JSON. Replace <NewModuleName> (in two places) with your own module name.

    "sensorTo<NewModuleName>": "FROM /messages/modules/SimulatedTemperatureSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/<NewModuleName>/inputs/input1\")"
    
  4. Right-click the main project (for example, AzureIotEdgeApp1) and select Set as StartUp Project. By setting the main project as the startup project, all modules in the solution run. This includes both modules you added to the solution, the simulated temperature sensor module, and the simulated Edge hub.

  5. Press F5 or select the run toolbar button to run the solution. It may take 10 to 20 seconds initially. Be sure you don't have other Docker containers running that might bind the port you need for this project.

  6. You should see two .NET Core console app windows appear one for each module.

  7. Set a breakpoint to inspect the modules.

    • If developing in C#, set a breakpoint in the PipeMessage() function in ModuleBackgroundService.cs.
    • If using C, set a breakpoint in the InputQueue1Callback() function in main.c.
  8. Create breakpoints in each module and then press F5 to run and debug multiple modules simultaneously. You should see multiple .NET Core console app windows, with each window representing a different module.

    Screenshot of Visual Studio with two output consoles.

  9. Press Ctrl + F5 or select the stop button to stop debugging.

Build and push images to registry

Once you've developed and debugged your module, you can build and push the module image to your Azure Container Registry. You can then deploy the module to your IoT Edge device.

  1. Set the main IoT Edge project as the start-up project, not one of the individual modules.

  2. Select either Debug or Release as the configuration to build for your module images.

    Note

    When choosing Debug, Visual Studio uses Dockerfile.(amd64|windows-amd64).debug to build Docker images. This includes the .NET Core command-line debugger VSDBG in your container image while building it. For production-ready IoT Edge modules, we recommend that you use the Release configuration, which uses Dockerfile.(amd64|windows-amd64) without VSDBG.

  3. If you're using a private registry like Azure Container Registry (ACR), use the following Docker command to sign in to it. You can get the username and password from the Access keys page of your registry in the Azure portal.

    docker login <ACR login server>
    
  4. Let's add the Azure Container Registry login information to the runtime settings found in the file deployment.debug.template.json. There are two ways to do this. You can either add your registry credentials to your .env file (most secure) or add them directly to your deployment.debug.template.json file.

    Add credentials to your .env file:

    In Solution Explorer, select the Show All Files toolbar button. The .env file appears. Add your Azure Container Registry username and password to your .env file. These credentials can be found on the Access Keys page of your Azure Container Registry in the Azure portal.

    Screenshot of button that shows all files in the Solution Explorer.

        DEFAULT_RT_IMAGE=1.2
        CONTAINER_REGISTRY_USERNAME_myregistry=<my-registry-name>
        CONTAINER_REGISTRY_PASSWORD_myregistry=<my-registry-password>
    

    Add credentials directly to deployment.debug.template.json

    If you'd rather add your credentials directly to your deployment template, replace the placeholders with your ACR admin username, password, and registry name.

          "settings": {
            "minDockerVersion": "v1.25",
            "loggingOptions": "",
            "registryCredentials": {
              "registry1": {
                "username": "<username>",
                "password": "<password>",
                "address": "<registry name>.azurecr.io"
              }
            }
          }
    

    Note

    This article uses admin login credentials for Azure Container Registry, which are convenient for development and test scenarios. When you're ready for production scenarios, we recommend a least-privilege authentication option like service principals. For more information, see Manage access to your container registry.

  5. If you're using a local registry, you can run a local registry.

  6. Finally, in the Solution Explorer, right-click the main project folder and select Build and Push IoT Edge Modules to build and push the Docker image for each module. This might take a minute. When you see Finished Build and Push IoT Edge Modules. in your Output console of Visual Studio, you're done.

Deploy the solution

Now that you've built and pushed your module images to your Azure Container Registry, you can deploy the solution to your IoT Edge device. You already have a deployment manifest template you've been observing throughout this tutorial. Let's generate a deployment manifest from that, then use an Azure CLI command to deploy your modules to your IoT Edge device in Azure.

  1. Right-click on your main project in Visual Studio Solution Explorer and choose Generate Deployment for IoT Edge.

    Screenshot of location of the 'generate deployment' menu item.

  2. Go to your local Visual Studio main project folder and look in the config folder. The file path might look like this: C:\Users\<YOUR-USER-NAME>\source\repos\<YOUR-IOT-EDGE-PROJECT-NAME>\config. Here you find the generated deployment manifest such as deployment.amd64.debug.json.

  3. Check your deployment.amd64.debug.json file to confirm the edgeHub schema version is set to 1.2.

     "$edgeHub": {
         "properties.desired": {
           "schemaVersion": "1.2",
           "routes": {
             "IotEdgeModule2022ToIoTHub": "FROM /messages/modules/IotEdgeModule2022/outputs/* INTO $upstream",
             "sensorToIotEdgeModule2022": "FROM /messages/modules/SimulatedTemperatureSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/IotEdgeModule2022/inputs/input1\")",
             "IotEdgeModule2022bToIoTHub": "FROM /messages/modules/IotEdgeModule2022b/outputs/* INTO $upstream"
           },
           "storeAndForwardConfiguration": {
             "timeToLiveSecs": 7200
           }
         }
       }
    

    Tip

    The deployment template for Visual Studio 2022 requires the 1.2 schema version. If you need it to be 1.1 or 1.0, wait until after the deployment is generated (do not change it in deployment.debug.template.json). Generating a deployment will create a 1.2 schema by default. However, you can manually change deployment.amd64.debug.json, the generated manifest, if needed before deploying it to Azure.

    Important

    Once your IoT Edge device is deployed, it currently won't display correctly in the Azure portal with schema version 1.2 (version 1.1 will be fine). This is a known bug and will be fixed soon. However, this won't affect your device, as it's still connected in IoT Hub and can be communicated with at any time using the Azure CLI.

    Screenshot of Azure portal error on the IoT Edge device page.

  4. Now let's deploy our manifest with an Azure CLI command. Open the Visual Studio Developer Command Prompt and change to the config directory.

        cd config
    
  5. Deploy the manifest for your IoT Edge device to IoT Hub. The command configures the device to use modules developed in your solution. The deployment manifest was created in the previous step and stored in the config folder. From your config folder, execute the following deployment command. Replace the [device id], [hub name], and [file path] with your values. If the IoT Edge device ID doesn't exist in the IoT Hub, it must be created.

        az iot edge set-modules --device-id [device id] --hub-name [hub name] --content [file path]
    

    For example, your command might look like this:

    az iot edge set-modules --device-id my-device-name --hub-name my-iot-hub-name --content deployment.amd64.debug.json
    
  6. After running the command, you'll see a confirmation of deployment printed in JSON in your command prompt.

Build module Docker image

Once you've developed your module, you can build the module image to store in a container registry for deployment to your IoT Edge device.

Use the module's Dockerfile to build the module Docker image.

docker build --rm -f "<DockerFilePath>" -t <ImageNameAndTag> "<ContextPath>" 

For example, let's assume your command shell is in your project directory and your module name is IotEdgeModule1. To build the image for the local registry or an Azure container registry, use the following commands:

# Build the image for the local registry

docker build --rm -f "./IotEdgeModule1/Dockerfile.amd64.debug" -t localhost:5000/iotedgemodule1:0.0.1-amd64 "./IotEdgeModule1"

# Or build the image for an Azure Container Registry

docker build --rm -f "./IotEdgeModule1/Dockerfile.amd64.debug" -t myacr.azurecr.io/iotedgemodule1:0.0.1-amd64 "./IotEdgeModule1"

Push module Docker image

Push your module image to the local registry or a container registry.

docker push <ImageName>

For example:

# Push the Docker image to the local registry

docker push localhost:5000/iotedgemodule1:0.0.1-amd64

# Or push the Docker image to an Azure Container Registry
az acr login --name myacr
docker push myacr.azurecr.io/iotedgemodule1:0.0.1-amd64

Deploy the module to the IoT Edge device.

In Visual Studio, open deployment.debug.template.json deployment manifest file in the main project. The deployment manifest is a JSON document that describes the modules to be configured on the targeted IoT Edge device. Before deployment, you need to update your Azure Container Registry credentials, your module images, and the proper createOptions values. For more information about createOption values, see How to configure container create options for IoT Edge modules.

  1. If you're using an Azure Container Registry to store your module image, you need to add your credentials to deployment.debug.template.json in the edgeAgent settings. For example,

    "modulesContent": {
    "$edgeAgent": {
      "properties.desired": {
        "schemaVersion": "1.1",
        "runtime": {
          "type": "docker",
          "settings": {
            "minDockerVersion": "v1.25",
            "loggingOptions": "",
            "registryCredentials": {
              "myacr": {
                "username": "myacr",
                "password": "<your_acr_password>",
                "address": "myacr.azurecr.io"
              }
            }
          }
        },
    //...
    
  2. Replace the image property value with the module image name you pushed to the registry. For example, if you pushed an image tagged myacr.azurecr.io/iotedgemodule1:0.0.1-amd64 for custom module IotEdgeModule1, replace the image property value with the tag value.

  3. Add or replace the createOptions value with stringified content for each system and custom module in the deployment template.

    For example, the IotEdgeModule1's image and createOptions settings would be similar to the following:

    "IotEdgeModule1": {
    "version": "1.0.0",
    "type": "docker",
    "status": "running",
    "restartPolicy": "always",
    "settings": {
        "image": "myacr.azurecr.io/iotedgemodule1:0.0.1-amd64",
        "createOptions": "{\"HostConfig\":{\"PortBindings\":{\"5671/tcp\":[{\"HostPort\":\"5671\"}],\"8883/tcp\":[{\"HostPort\":\"8883\"}],\"443/tcp\":[{\"HostPort\":\"443\"}]}}}"
    }
    

Use the IoT Edge Azure CLI set-modules command to deploy the modules to the Azure IoT Hub. For example, to deploy the modules defined in the deployment.debug.amd64.json file to IoT Hub my-iot-hub for the IoT Edge device my-device, use the following command:

az iot edge set-modules --hub-name my-iot-hub --device-id my-device --content ./deployment.debug.template.json --login "HostName=my-iot-hub.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=<SharedAccessKey>"

Tip

You can find your IoT Hub connection string in the Azure portal under Azure IoT Hub > Security settings > Shared access policies.

Confirm the deployment to your device

To check that your IoT Edge modules were deployed to Azure, sign in to your device (or virtual machine), for example through SSH or Azure Bastion, and run the IoT Edge list command.

   iotedge list

You should see a list of your modules running on your device or virtual machine.

   NAME                        STATUS           DESCRIPTION      CONFIG
   SimulatedTemperatureSensor  running          Up a minute      mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0
   edgeAgent                   running          Up a minute      mcr.microsoft.com/azureiotedge-agent:1.2
   edgeHub                     running          Up a minute      mcr.microsoft.com/azureiotedge-hub:1.2
   IotEdgeModule1              running          Up a minute      myacr.azurecr.io/iotedgemodule1:0.0.1-amd64.debug
   myIotEdgeModule2            running          Up a minute      myacr.azurecr.io/myiotedgemodule2:0.0.1-amd64.debug

Debug using Docker Remote SSH

The Docker and Moby engines support SSH connections to containers allowing you to attach and debug code on a remote device using Visual Studio.

  1. Connecting remotely to Docker requires root-level privileges. Follow the steps in Manage docker as a non-root user to allow connection to the Docker daemon on the remote device. When you're finished debugging, you may want to remove your user from the Docker group.

  2. Follow the steps to use Visual Studio to Attach to a process running on a Docker container on your remote device.

  3. In Visual Studio, set breakpoints in your custom module.

  4. When a breakpoint is hit, you can inspect variables, step through code, and debug your module.

    Screenshot of Visual Studio attached to remote docker container on a device paused on a breakpoint.

Next steps