Project dependencies when using a multi project workspace

I am using VS code with the Torizon extension.
I have created a workspace hmicollective with 2 web assembly projects running in their own docker containers.
I require the ability to communicate between the two containers.
I have create an API in one and can send http put/get messages successfully from the other.

Lets say the target is a CAN interface controller and the source is a GUI front end.
So, press a button on the screen and a CAN message is sent to do some work.

I now need to be able to create the API frame work in the target so I can analyse the content of the API messages arriving.

This means I need to reference the target project from the source project so that I use a single place to declare models etc.

So, I reference the caninterface project in the hmifrontend project and intel sense works nicely.

However, when I compile the source which references the target I get:

 Skipping project "/home/torizon/caninterface/caninterface.csproj" because it was not found.

and

9.721 /home/torizon/app/Program.cs(5,21): error CS0234: The type or namespace name 'caninterface' does not exist in the namespace 'hmicollective' (are you missing an assembly reference?) [/home/torizon/app/hmifrontend.csproj]

Each project compiles nicely on its own, but when it requires another project to reference the other project’s source this not included.

Is there a recommended way to achieve this?
or
Do I have to duplicate the source for the API in both projects and maintain it twice?

Regards
Lloyd

Hi, I’m the Toradex AI Assistant.

I might make mistakes or occasionally get you wrong. Our support team will step in if needed.


I’d be glad to help with your multi-container project setup! However, to provide the most accurate guidance for your specific configuration, could you let me know which OS version you’re running on your target device? You can use the tdx-info command to get this information - more details available at Getting Device Information with Tdx-Info | Toradex Developer Center

Common platforms include:

  • Hardware: Verdin i.MX8M Plus, Verdin AM62, Aquila AM69, Apalis i.MX8QP, Colibri i.MX8X
  • OS: Torizon OS 6, Torizon OS 7.2.0-build.13, BSP 7, BSP 6
  • Implementation: Docker, .NET/C#, WebAssembly

Understanding Multi-Container Project Structure

Based on the documentation, your multi-container workspace follows the Torizon IDE extension’s multi-root workspace structure(1). In a multi-container project, the root folder contains files specific to the multi-container project, along with separate folders for each single-container project(1).

Project Reference Challenges

The issue you’re experiencing is common when trying to share code between containerized projects. Each container builds in isolation, so direct project references between containers don’t work the same way as in traditional .NET solutions.

Recommended Approaches

1. Shared Library Approach

Create a separate shared library project within your workspace that contains your common API models and interfaces. Both your GUI frontend and CAN interface projects can reference this shared library.

2. Docker Volume Mounting

The multi-container project uses docker-compose.yml to configure services, build parameters, and volume mounting(2). You can mount shared source directories between containers during the build process.

3. NuGet Package Approach

Package your shared models as a NuGet package that both projects can reference independently.

Once you provide your OS version information, I can give you more specific implementation guidance for your WebAssembly and CAN interface setup.

Best regards, Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.

@ToradexAI

her is the device/OS info

Software summary
------------------------------------------------------------
Bootloader:               U-Boot
Kernel version:           6.6.119-7.5.0-00057-g86497e203ba0 #1-Torizon SMP PREEMPT Mon Jun 30 14:11:55 UTC 2025
Kernel command line:      root=LABEL=otaroot rootfstype=ext4 quiet logo.nologo vt.global_cursor_default=0 plymouth.ignore-serial-consoles splash fbcon=map:3 ostree=/ostree/boot.0/torizon/947b90f5159fced3e491e121c1c98ccfe5727fa6e37548cfb450b0303924db64/0
Distro name:              NAME="Torizon OS Upstream"
Distro version:           VERSION_ID=7.5.0-build.30
Distro variant:           VARIANT="Docker"
Hostname:                 colibri-imx7-emmc-07311078
------------------------------------------------------------

Hardware info
------------------------------------------------------------
HW model:                 Toradex Colibri iMX7D 1GB (eMMC) on Colibri Evaluation Board V3
Toradex version:          0039 V1.1B
Serial number:            07311078
Processor arch:           armv7l
------------------------------------------------------------

Thank you for providing the device information! You’re running Torizon OS 7.5.0 on a Colibri iMX7D. Now I can provide specific guidance for your multi-container .NET project setup.

Project Reference Solutions for Multi-Container Workspaces

For your WebAssembly projects sharing API models between the GUI frontend and CAN interface containers, here are the recommended approaches:

1. Shared Library with File Mounting

The most effective solution is to create a shared library and use file mounting between your workspace and containers. In multi-container projects, the SDK container mounts the project’s workspace into the container, so no additional configuration is required for the build process(1).

For your debug containers, you’ll need to add files to containers using the COPY command in Dockerfile.debug(1):

COPY --chown=$SSHUSERNAME:$SSHUSERNAME ./shared-models ${APP_ROOT}/shared-models

(1)

2. Adding Files Shared Between Projects

Since you have multiple projects that need to share code, you can create a shared library outside your individual project workspaces. The documentation specifically addresses this scenario(1).

Create a synchronization task in your tasks.json file using rsync(1):

{
  "label": "rsync-shared-models",
  "detail": "",
  "hide": true,
  "command": "rsync",
  "type": "process",
  "args": [
    "-av",
    "../shared-api-models",
    "./shared-models"
  ],
  "dependsOrder": "sequence",
  "problemMatcher": "$msCompile"
}

(1)

3. Multi-Container Docker Compose Configuration

Your multi-container project uses docker-compose.yml to orchestrate execution of all single-container applications together(2). You can modify this file to include volume mounts for shared source code:

services:
  hmifrontend:
    build:
      context: .
      dockerfile: hmifrontend/Dockerfile
    volumes:
      - ./shared-models:/app/shared-models
    depends_on:
      - caninterface
  
  caninterface:
    build:
      context: .
      dockerfile: caninterface/Dockerfile
    volumes:
      - ./shared-models:/app/shared-models

(2)

4. Container Communication

For inter-container communication, Docker Compose sets up a single network for your app by default. Each container is discoverable by other containers at a hostname identical to the container name(3). This explains why your HTTP communication between containers is working successfully.

5. NuGet Package Management

For .NET packages in your projects, you can use the standard approach. To add .NET packages, run the following command in VS Code’s terminal(1):

dotnet add package <PackageName>

(1)

Recommended Implementation Steps

  1. Create a shared-models directory in your workspace root
  2. Move your common API models to this shared directory
  3. Add the rsync task to synchronize the shared models to each project
  4. Update your Dockerfile.debug files to copy the shared models
  5. Modify your project references to use the local shared models path

This approach avoids code duplication while maintaining the containerized architecture that Torizon multi-container projects require.

Best regards, Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.

Hi @Lloyd,

Is your project a multi-container one? If yes how is it structured? For example, through a docker- compose file? Could you share your docker-compose file with us? Ideally, a minimal project or steps to reproduce the issue would be nice.

Hi @rudhi.tx
Thanks for getting back to me.
It is a standard Torizon extension multi container project.
Here are the relevant sections of the docker-compose.yml file.
I have left out the cog/weston services.

services:
  hmifrontend:
    build:
      context: .
      dockerfile: Dockerfile
    image: ${DOCKER_LOGIN}/hmifrontend:${TAG}
    ports:
      - 7001:7001
    profiles:
      - release

and

services:
  caninterface:
    build:
      context: .
      dockerfile: Dockerfile
    image: ${DOCKER_LOGIN}/caninterface:${TAG}
    ports:
      - 7009:7001
    profiles:
      - release

The line that causes a problem is from the Dockerfile in the hmifrontend project:

COPY . ${APP_ROOT}

This only pulls the folder in for the hmifrontend container the Dockerfile is in. The definition and code of the API is not stored in this folder so when the content of this folder is copied into the build container the caninterface folder the API is defined in is missing.
The folder structure is like this, I have left out much of the caninterface to reduce size.

.
├── caninterface
│   ├── API
│   ├── docker-compose.yml
│   ├── Dockerfile
│   ├── Dockerfile.debug
│   .....
└── hmifrontend
    ├── App.razor
    ├── appsettings.Development.json
    ├── appsettings.json
    ├── bin
    ├── Classes
    ├── Controls
    ├── Data
    ├── docker-compose.yml
    ├── Dockerfile
    ├── Dockerfile.debug
    ├── hmifrontend.code-workspace
    ├── hmifrontend.csproj
    ├── hmifrontend.sln
    ├── _Imports.razor
    ├── Models
    ├── obj
    ├── Pages
    ├── Program.cs
    ├── Properties
    ├── README.md
    ├── Shared
    ├── torizonPackages.json
    └── wwwroot

The API definition would either be in the target container or in the API library project if I moved to the AI’s workflow suggestion.
Either way there is no method of adding a dependency to another project and accessing the project via a dotnet project dependency.

I think I would need a way to build the target container from the parent folder, which would allow all the container folders to be visible to the build container and then only copy the built folder as done now.

COPY --from=build ${APP_ROOT}/bin/Release/net10.0/linux-${IMAGE_ARCH}/publish ${APP_ROOT}

So, what the Torizon extension development eco system is missing is tasks with the ability to build the system from the multi project root folder allowing dependencies between projects.

Without this even the use of an API library is flawed as this is also an independent project.
The dirty workaround thus becomes copying the libraries dll into an area where it can be included when the individual source project build takes place.

I understand this is a complicated answer to my build requirements, if you want to reach out and discuss in a video call to better understand I would be happy to hear from you.

Regards
Lloyd

Hi @rudhi.tx

So I have made a task at the project root level:

        {
            "label": "build-hmifrontend-container-torizon-release-arm",
            "detail": "",
            "hide": false,
            "command": "DOCKER_HOST=",
            "type": "shell",
            "options": {
                "env": {
                    "DOCKER_LOGIN": "localhost:5002",
                    "TAG": "${config:docker_tag}",
                    "GPU": "${config:torizon_gpu}",
                    "APP_ROOT": "${config:torizon_app_root}",
                    "DEBUG_SSH_PORT": "${config:torizon_debug_ssh_port}",
                    "DEBUG_PORT1": "${config:torizon_debug_port1}",
                    "DEBUG_PORT2": "${config:torizon_debug_port2}",
                    "DEBUG_PORT3": "${config:torizon_debug_port3}"
                }
            },
            "args": [
                "docker",
                "compose",
                "build",
                "--pull",
                "--build-arg",
                "SSHUSERNAME=${config:torizon_run_as}",
                "--build-arg",
                "APP_ROOT=${config:torizon_app_root}",
                "--build-arg",
                "IMAGE_ARCH=arm",
                "--build-arg",
                "GPU=${config:torizon_gpu}",
                "hmifrontend"
            ],
            "dependsOrder": "sequence",
            "dependsOn": [  


                "wait-a-bit"    
            ],
            "problemMatcher": "$tsc",
            "icon": {
                "id": "tools",
                "color": "terminal.ansiYellow"
            },
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": true,
                "panel": "shared",
                "group": "build-execution"
            }
        },
    ],

and a docker file hmifrontend-Dockerfile for the hmifrontend:

# ARGUMENTS --------------------------------------------------------------------
##
# Board architecture
##
ARG IMAGE_ARCH=

##
# Base container version
##
ARG BASE_VERSION=4

##
# Directory of the application inside container
##
ARG APP_ROOT=
# ARGUMENTS --------------------------------------------------------------------



# BUILD ------------------------------------------------------------------------
#FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build

ARG IMAGE_ARCH
ARG APP_ROOT

COPY . ${APP_ROOT}
WORKDIR ${APP_ROOT}

# build
RUN dotnet restore hmifrontend/hmifrontend.csproj 
RUN if [ "$IMAGE_ARCH" = "arm64" ] ; then \
        export ARCH=${IMAGE_ARCH} ; \
    elif [ "$IMAGE_ARCH" = "armhf" ] ; then \
        export ARCH="arm" ; \
    elif [ "$IMAGE_ARCH" = "amd64" ] ; then \
        export ARCH="x64" ; \
    elif [ "$IMAGE_ARCH" = "arm" ] ; then \
        export ARCH="arm" ; \
    fi
RUN dotnet publish hmifrontend/hmifrontend.csproj -c Release -r linux-arm --no-self-contained

RUN if [ "./hmifrontend/bin/Release/net10.0/linux-arm" != "./hmifrontend/bin/Release/net10.0/linux-arm" ]; then \
        mv ./hmifrontend/bin/Release/net10.0/linux-${ARCH} ./hmifrontend/bin/Release/net10.0/linux-${IMAGE_ARCH}; \
    fi

# BUILD ------------------------------------------------------------------------



# DEPLOY -----------------------------------------------------------------------
FROM --platform=linux/${IMAGE_ARCH} \
    torizon/aspdotnet10:${BASE_VERSION} AS deploy

ARG IMAGE_ARCH
ARG APP_ROOT

RUN apt-get -y update && apt-get install -y --no-install-recommends \
    # ADD YOUR PACKAGES HERE
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    # __torizon_packages_prod_start__
    # __torizon_packages_prod_end__
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
	&& apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*


# Copy the application compiled in the build step to the $APP_ROOT directory
# path inside the container, where $APP_ROOT is the torizon_app_root
# configuration defined in settings.json
COPY --from=build ${APP_ROOT}/hmifrontend/bin/Release/net10.0/linux-${IMAGE_ARCH}/publish ${APP_ROOT}

# "cd" (enter) into the APP_ROOT directory
WORKDIR ${APP_ROOT}

# Command executed in runtime when the container starts
# CMD ["/bin/sh"]
CMD ["./hmifrontend"]

# DEPLOY -----------------------------------------------------------------------

this is called from the docker-compose.yml

services:
  hmifrontend:
    image: ${DOCKER_LOGIN}/hmifrontend:${TAG}
    build:
      context: .
      dockerfile: hmifrontend_Dockerfile
    ports:
    - 7001:7001
    profiles:
    - release

This allows me to copy the whole multi container folder structure to the build container and this allows project dependencies to be honoured at the dotnet command level.

It would be nice if the Torizon eco system had tasks at the multi project level or something similar, as well as a Torizon Class Library Project with build tasks.

Regards
Lloyd

Hi @rudhi.tx

OK, after further investigation I found that I was able to compile and run a container that was dependent on another project such as a class library project.

However, the above approach in my previous post is not the best way of doing it.

I found that debugging the project enabled me to build a container that had code in it from other projects. that’s because the build process is different. It builds the project separately and then links it to the debug container.

So, a better approach is to build the release code in a similar manner to the debug workflow and then add it to a release container in a similar way as the debug builds.

I will be looking at this as a potential workflow.

Regards
Lloyd

@rudhi.tx
Hi

Is there a way to change the build process for release builds to be similar to the build process for debug builds?

publish-debug-arm
build-container-torizon-debug-arm
apply-torizon-packages-arm
push-container-torizon-debug-arm

The “publish-debug-arm” step does a dotnet build outside of docker and this gets added to the debug container in later steps.

If I make this change is there a reason why this is not a good idea?

Regards
Lloyd

Ok, so I have minimised the changes needed to be made to my build scripts.

Task run-container-torizon-release-arm has a new dependancy added called publish-release-arm
Its depends on section is now

            "dependsOn": [
                "template-specific-initial-task",
                "validate-settings",
                "validate-arch-arm",
                "copy-docker-compose",
                "pre-cleanup",
                "publish-release-arm",
                "build-torizon-release-arm",
                "push-container-torizon-release",
                "pull-container-torizon-release",
                "template-specific-final-task",
                "wait-a-bit"
            ],

and the new task is

        {
            "label": "publish-release-arm",
            "detail": "dotnet publish command for building the release arm target",
            "command": "dotnet",
            "type": "process",
            "args": [
                "publish",
                "${workspaceFolder}/hmifrontend.csproj",
                "/property:GenerateFullPaths=true",
                "/consoleloggerparameters:NoSummary",
                "-c",
                "Release",
                "-r",
                "linux-arm",
                "--no-self-contained"
            ],
            "dependsOrder": "sequence",
            "problemMatcher": "$msCompile",
            "icon": {
                "id": "tools",
                "color": "terminal.ansiYellow"
            },
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": true,
                "panel": "shared",
                "group": "build-execution"
            }
        }

This needs a Dockerfile with the cut down script:

##
# Directory of the application inside container
##
ARG APP_ROOT=
# ARGUMENTS --------------------------------------------------------------------

# DEPLOY -----------------------------------------------------------------------
FROM --platform=linux/${IMAGE_ARCH} \
    torizon/aspdotnet10:${BASE_VERSION} AS deploy

ARG IMAGE_ARCH
ARG APP_ROOT

RUN apt-get -y update && apt-get install -y --no-install-recommends \
    # ADD YOUR PACKAGES HERE
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
    # __torizon_packages_prod_start__
    # __torizon_packages_prod_end__
# DO NOT REMOVE THIS LABEL: this is used for VS Code automation
	&& apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

# Copy the application compiled in the hosts project directory
# path inside the container, where $APP_ROOT is the torizon_app_root
# configuration defined in settings.json
COPY bin/Release/net10.0/linux-${IMAGE_ARCH}/publish ${APP_ROOT}

# "cd" (enter) into the APP_ROOT directory
WORKDIR ${APP_ROOT}

# Command executed in runtime when the container starts
CMD ["./hmifrontend"]
# DEPLOY -----------------------------------------------------------------------

These scripts probably need to have environment variables and other items to make them generic but they produce a container with the relevant class libraries from other projects in the apps folder.

I am about to test that the class library is correct and called ok.

Is there anything you can see with this approach that could be incorrect?

I have also updated my projects to .net10

Regards
Lloyd

Hi @Lloyd,

Sorry about the delay in getting back. Also, thanks for your extensive test results. Since giving you a recommendation here is beyond my know-how with the multi-container projects, I am requesting our VSCode extension developers to help you directly here.

Is the project now compiling successfully with your workaround with the new task you created? Also, which version of VSCode and Torizon extension are you using?

Hi @rudhi.tx

Yes I have a work flow running which includes the changes listed, it publishes using dotnet outside the container like the debug process and then combines the publish later. This means I have been able to continue with development using inter container communication using class library projects.
I use a Ubuntu virtual machine with
VS code version 1.116.0
Torizon IDE extension 3.4.0

I do have to execute a lot of manual steps when I need to add a new project to the multi project workspace.

Update solution files, update code-workspaces, create the project outside the multi project workspace and then copy it into the folder space etc.

Regards
Lloyd