Simple approach to run and debug multiple .NET Dapr projects (w/o Docker)
Tags: dapr, dotnet, powershell
Published: 2020-08-21 14:40:18

---
title: Simple approach to run and debug multiple .NET Dapr projects (w/o Docker)
published: true
description: Locally executing and debugging multiple Dapr applications with PowerShell background jobs instead of Docker containers.
tags: #dapr #dotnet #powershell
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/ldn02vnwskt3ddssg6qt.JPG
---

# TL;DR

One way of executing multiple Dapr applications locally is to use [Docker compose](https://github.com/dapr/samples/tree/master/hello-docker-compose) or to use [project Tye](https://github.com/dotnet/tye) with its Dapr specific features. Both approaches base on packaging the apps into containers and start these containers. Additionally a network boundary is created to avoid or tune down "port dance" mentioned below. Debugging then is achieved by attaching to the container executing the Dapr application.

To avoid container build times and attaching the debugger - given that the setup is homogenous and/or simple enough - in this post I want to show [a simple approach to start Dapr sidecars and (selectively) application instances with PowerShell (Core) background jobs](https://github.com/KaiWalter/dapr-experimental/blob/master/app2app/startDaprAsJob.ps1).

> DISCLAIMER: the approach shown here is not intended for production use; it is intended to help with local debugging and testing of .NET based Dapr applications

# startDaprAsJob script

[This repository](https://github.com/KaiWalter/dapr-experimental/tree/master/app2app) contains a simple 2 app sample - app1 invoking app2 - to show the basic workings of this approach. Script `./startDaprAsJob.ps1` controls which projects / applications are to be started. Again for simplicity a static definition is used which could be replaced with some generic function to detect all relevant projects and settings.

> pre-requisite: Dapr client is initialized with `dapr init --slim` so that the script can control lifecycle of placement service, which is required for actor scenarios

## challenge - the "port dance"

When running multiple Dapr applications / services locally in parallel the challenge is, that each needs a dedicated set of ports for the application itself, for Dapr HTTP, for Dapr gRPC and also for Dapr metrics. The script solves this by generating port numbers dynamically and assigning these to the particular applications through modifying the `launchSettings.json`.

Now this adjustment makes the script somewhat specific to .NET and Visual Studio. But this approach can be used for adapting the script to any other environment. 

## configuration

```
$configProjects = @(
    @{
        appId       = "app1"
        folder      = "./app1"
        projectFile = "app1.csproj"
        settingName = "app1"
        debug       = $false
    }
    @{
        appId       = "app2"
        folder      = "./app2"
        projectFile = "app2.csproj"
        settingName = "app2"
        debug       = $true
    }
)
```

| setting | purpose |
| ---- | ---- |
| `appId` | needs to be Dapr id commonly used to address service |
| `folder` | relative folder of .NET project (containing components folder and `tracing.yaml`) |
| `projectFile` | name of service project file |
| `settingName` | name of launch setting (Launch:Project; not IIS Express) which is modified for startup |
| `debug` | `$true` : no background instance is started; waiting for (debugging) instance started from VS<br/>`$false`  : background instance of service is started with dotnet run |

Setting `debug` determines which projects are directly executed as background jobs (when `$false`) or which ones are started by yourself from a development environment (when `$true`). For those apps Dapr sidecar(s) wait(s) until application(s) become available.

## usage

With the settings above `app2` can be opened e.g. in VS, marked as startup project, desired breakpoints (for example in line 21 of `app2` `HealthController`) can be set and be started.

Now the script is used to start the other Dapr applications / services / ... 

```
❯ .\startDaprAsJob.ps1

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command
--     ----            -------------   -----         -----------     --------             -------
41     placement       BackgroundJob   Running       True            localhost            …
--------------------------------------------------------------------------------
updated C:\Users\kai\src\dapr-experimental\app2app\app1\Properties\launchSettings.json
start Daprd in background app1 5000
43     app1-daprd      BackgroundJob   Running       True            localhost            …
45     app1-app        BackgroundJob   Running       True            localhost            …
--------------------------------------------------------------------------------
updated C:\Users\kai\src\dapr-experimental\app2app\app2\Properties\launchSettings.json
start Daprd in background app2 5010
47     app2-daprd      BackgroundJob   Running       True            localhost            …
expecting C:\Users\kai\src\dapr-experimental\app2app\app2\app2.csproj to be started from development environment
--------------------------------------------------------------------------------
t: test call health endpoint
s: job status
e: check all logs for errors
q: stop jobs and quit
0: show log of placement
1: show log of app1-daprd
2: show log of app1-app
3: show log of app2-daprd
Enter option:
```

Checking job status shows that `app1` is running with its sidecar and for `app2` only the sidecar is running.

```
Enter option: s


Name       State
----       -----
placement  Running
app1-daprd Running
app1-app   Running
app2-daprd Running
```

Invoking the test should bring up `app2` in debugger:

```
Enter option: t
--------------------------------------------------------------------------------
App1 health

status : OK

App2 health (through App1)
...trip to debugger...
status : OK - App2
```

Breakpoint hit:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lmib8vqorboxt9wieh5h.png)

That's it.

## other options

`e` does a basic check for errors on all logs produced to see whether Dapr sidecars and components started or whether errors occurred in the applications.

As a convenience numbers can be used to retrieve logs of the corresponding job.

`1` would display the Dapr log of `app1`:

```
time="2020-08-21T13:54:58.6704251Z" level=info msg="starting Dapr Runtime -- version 0.9.0 -- commit 6babe85-dirty" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6704251Z" level=info msg="log level set to: debug" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.671422Z" level=info msg="metrics server started on :9091/" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.metrics type=log ver=0.9.0
time="2020-08-21T13:54:58.6774253Z" level=info msg="standalone mode configured" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6774253Z" level=info msg="app id: app1" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6774253Z" level=info msg="mTLS is disabled. Skipping certificate request and tls validation" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6794256Z" level=info msg="found component statestore (state.redis)" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6794256Z" level=info msg="found component messagebus (pubsub.redis)" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:54:58.6794256Z" level=info msg="application protocol: http. waiting on port 5000.  This will block until the app is listening on that port." app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:55:00.9266468Z" level=info msg="application discovered on port 5000" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:55:00.9925923Z" level=info msg="application configuration loaded" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.runtime type=log ver=0.9.0
time="2020-08-21T13:55:01.0135957Z" level=info msg="local service entry announced: app1 -> 10.242.0.4:63838" app_id=app1 instance=cz-kw-w10-2004 scope=dapr.contrib type=log ver=0.9.0
...
```

`2` would display `app2` console log:

```
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Users\kai\src\dapr-experimental\app2app\app1
```

Option `q` stops and removes all related jobs. In case the script breaks, jobs can be cleaned up by starting the script again with immediately ending it with `q`.

## supported OS

The only Windows OS specific part in this script should be the invocation of the `placement` service:

```
    C:\Dapr\placement.exe --port $port
```

[How to run Dapr self hosted without containers](https://github.com/dapr/docs/tree/master/howto/self-hosted-no-docker) indicates what needs to be adapted for Linux / MacOS.

# Conclusion

Whether it is easier and more stable to have **Docker containers** (_see above mentioned [Docker compose](https://github.com/dapr/samples/tree/master/hello-docker-compose) and [project Tye](https://github.com/dotnet/tye)_) or **PowerShell jobs** floating around on your machine is a matter of taste. I personally like this approach as it allows me to spin up a multi application scenario on my local machine quickly.