Distributed Application Runtime (Dapr): Quick start on .NET 7
Here is a quick tutorial on how to start working with Dapr from dev’s perspective. Dapr is supported by an ever-growing community and has comprehensive documentation. I wouldn’t explain how Dapr works in this post however your understanding of Dapr at least superficial is required. So how to get to it quickly?
Environment:
- MacBook M1 Pro (arm64)
- Visual Studio Code + Dapr extension + C# extension (powered by OmniSharp)
- .NET SDK
- Dapr .NET SDK (Dapr SDK languages)
- Docker Desktop
We’ll start with a simple webapi application with the following capacity:
- A “Hello world” HTTP endpoint
- Publishing to RabbitMQ
- Subscribing to RabbitMQ
But let’s install self-hosted Dapr on our hosting platform beforehand using dapr cli:
# this command will download all required binaries for daprd to operate properly
dapr init --slim
slim argument allows us to exclude unnecessary containers like placement service, Redis, and Zipkin from self-hosted installation, they can be added at any time later
Now let’s provision our docker with RabbitMQ container:
# this command will download an image if not exist, run container and forward ports 15672 (management) and 5672 (AMQP)
docker run -d --name dapr_rabbitmq -p 15672:15672 -p 5672:5672 arm64v8/rabbitmq:3-management
I opted-in an image specifically for arm64 (feel free to choose the one that suits your CPU architecture)
We’re almost done with preparation and now we need to create a webapi project:
# create a webapi project based on webapi template
dotnet new webapi -n DaprAppSample -minimal --no-https
When we open the project in VScode for the first time we’re asked to create build & debug assets or alternatively, we can do this by:
Let’s run the project (F5) and see if it’s working (the project is pre-added with a /weatherforecast endpoint).
Dapr integration into project
Now, when we have created the project and installed Dapr, is the time to tie them up. For that let’s adjust our build & debug assets.
Adding two tasks that are needed for running our application properly in Dapr runtime:
# tasks.json
{
"version": "2.0.0",
"tasks": [
...
{
"appId": "daprappsampleid",
"appPort": 5001,
"label": "dapr-debug",
"type": "dapr",
"dependsOn": "build",
"appProtocol": "http",
"httpPort": 6001,
"grpcPort": 6002,
// Dapr component path is for adding our dapr configuration files
"componentsPath": "./${workspaceFolder}/daprcomponents"
},
{
"appId": "daprappsampleid",
"label": "daprd-down",
"type": "daprd-down"
}
]
}
Modifying the launching configuration that will perform these two tasks before running application:
{
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
// "build" changed to "dapr-debug"
"preLaunchTask": "dapr-debug",
// post debug task
"postDebugTask": "daprd-down",
"program": "${workspaceFolder}/DaprAppSample/bin/Debug/net7.0/DaprAppSample.dll",
"args": ["--urls","http://localhost:5001/"],
"cwd": "${workspaceFolder}/DaprAppSample",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
// dapr specific env vars
"DAPR_HTTP_PORT": "6001",
"DAPR_GRPC_PORT": "6002"
}
}
]
}
Adding Dapr components by creating a new folder daprcomponents in the working directory and creating a pubsub.yaml file inside:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
version: v1
type: pubsub.rabbitmq
metadata:
- name: host
value: "amqp://guest:guest@localhost:5672"
NOTE: There is a Dapr extension for VScode that can help us with the scaffolding of Dapr tasks and components
NOTE: If an IDE does not support such a pre-launch task it’s just required to run a side-car separately (before running an app) by:
dapr run --app-id daprappsampleid --app-port 5001 --app-protocol http --components-path ./DaprComponents --dapr-grpc-port 6002 --dapr-http-port 6001
That’s it, and now if we run our application it’ll be running in Dapr runtime. Little left to do: just adding package references and publishing/consuming our first message:
<PackageReference Include="Dapr.Client" Version="1.9.0" />
<PackageReference Include="Dapr.AspNetCore" Version="1.9.0" />
And eventually, there is a program.cs file:
using Dapr;
using Dapr.Client;
var builder = WebApplication.CreateBuilder(args);
// Dapr DI
builder.Services.AddDaprClient();
var app = builder.Build();
// Dapr cloud event middleware unwrap requests with cloud events structure
app.UseCloudEvents();
// Maps an endpoint that will respond to requests to /dapr/subscribe
app.MapSubscribeHandler();
app.MapGet("/hello", () => {
var daprClient = app.Services.GetRequiredService<DaprClient>();
daprClient.PublishEventAsync("pubsub",
"message",
new HelloRecord(Guid.NewGuid(),"Hello Dapr!"));
return Results.Ok("Hello Dapr!");
});
// an endpoint that will process messages consumed by pubsub
app.MapPost("/checkout", [Topic("pubsub","message")] (HelloRecord record) => {
Console.WriteLine($"Received a record with ID: {record.Id} and Text: {record.Message}");
return Results.Ok();
});
app.Run();
record HelloRecord(Guid Id, string Message);
This is a programmatical sample of the subscription, tbh I’d prefer better a declarative way using .yaml configurations.
GitHub repo: https://github.com/anurmatov/dapr-samples
I’m going to expand on the Dapr topic and have a series of articles soon to consolidate my knowledge about Dapr.
Article series about Dapr
- Distributed Application Runtime (Dapr): Quick start on .NET 7 (current post)
- Dapr: pubsub under the hood with RabbitMQ
References:
- https://dapr.io/