Skip to main content
SEMastery
DevOpsbeginner

Getting Started With Dapr for Building Cloud-Native Microservices in .NET

A beginner-friendly guide to Dapr for .NET developers: learn sidecars, state, pub/sub, and service invocation to build cloud-native microservices.

12 min readUpdated April 16, 2026

Building one program is easy. Building many small programs that must talk to each other over a network is hard. Messages get lost. One service is slow. Another one restarts. Where do you store data? Which message queue do you use? These problems show up again and again, and every team solves them from scratch.

Dapr was made to stop that repeating pain. In this guide you will learn what Dapr is, why .NET developers like it, and how to use its main features with simple C# code. We will keep the language plain and the steps small.

A simple everyday analogy

Think about a busy tiffin (lunchbox) delivery service in a big city like Mumbai. The cook makes the food. But the cook does not personally know every road, every train, or every customer's address. Instead, each cook has a delivery helper standing right next to the kitchen. The cook just hands the tiffin to the helper and says "send this to Mr. Rao." The helper figures out the route, handles the train, retries if the door was locked, and brings back the empty box.

The cook focuses on cooking. The helper handles the messy delivery world.

Dapr is that delivery helper for your microservice. Your .NET app is the cook. It focuses on business logic. Dapr stands right beside it and handles the messy network world: finding other services, retrying failed calls, saving data, and sending messages. Your app just says "do this" and Dapr does the hard part.

This "helper standing beside the app" is called a sidecar. Remember that word. It is the heart of Dapr.

What exactly is Dapr?

Dapr stands for Distributed Application Runtime. It is free, open source, and a graduated project of the Cloud Native Computing Foundation (CNCF), which means it is trusted and battle-tested in real production systems.

Dapr gives you a set of ready-made building blocks for the common problems every microservice faces. You call these building blocks over a simple, standard API. Dapr then talks to the real tool behind the scenes (Redis, Kafka, Azure Service Bus, and so on). If you change the tool later, your code does not change. Only a small config file changes.

Your .NET app talks to its Dapr sidecar, and the sidecar talks to the real infrastructure.

The key idea: your app talks to Dapr, not directly to the infrastructure. That swap of "real tool" for "standard API" is what makes Dapr powerful.

Why .NET developers like Dapr

There is a clean Dapr SDK for .NET. It feels like normal C#. You add a NuGet package, inject a DaprClient, and call methods. No deep network code.

Here is a quick comparison of life with and without Dapr.

ProblemWithout DaprWith Dapr
Calling another serviceHard-code URLs, write retry codeOne method call, retries built in
Saving statePick and wire a database SDKOne SaveStateAsync call
Sending messagesLearn a broker's SDK fullyOne PublishEventAsync call
Changing the brokerRewrite codeEdit a YAML file
SecretsCustom secret loadingStandard secrets API

A nice bonus: Dapr is not one of those .NET tools that recently moved to a paid commercial license. Some popular libraries like MediatR and MassTransit are now commercially licensed for many uses. Dapr stays free and open source. That matters when you plan budgets for a real project.

The building blocks you will use most

Dapr has many building blocks, but beginners use these four the most:

  1. Service invocation — call another service safely.
  2. State management — save and read key/value data.
  3. Publish & subscribe (pub/sub) — send and receive messages.
  4. Secrets — read passwords and keys safely.

The four starter building blocks

Invoke
State
PubSub
Secrets

Steps

1

Invoke

Call another service

2

State

Save key/value data

3

PubSub

Send and get messages

4

Secrets

Read keys safely

Start with these. The rest can wait.

Let's look at each one with code.

Setting up: install the tools

First, install the Dapr CLI and initialize Dapr on your machine. This sets up the local pieces (including a small Redis container for state and pub/sub).

// These are shell commands, shown here for clarity.
// 1. Install the Dapr CLI (see docs for your OS).
// 2. Initialize Dapr locally:
//    dapr init
// 3. Add the .NET SDK to your ASP.NET Core project:
//    dotnet add package Dapr.AspNetCore

In Program.cs, register Dapr so you can inject DaprClient anywhere.

var builder = WebApplication.CreateBuilder(args);
 
// Adds DaprClient to dependency injection.
builder.Services.AddControllers().AddDapr();
 
var app = builder.Build();
 
// Lets Dapr deliver pub/sub messages and cloud events to your app.
app.UseCloudEvents();
app.MapSubscribeHandler();
 
app.MapControllers();
app.Run();

Now you are ready to use the building blocks.

Building block 1: Service invocation

Imagine an Order service that needs to ask a Payment service to charge a customer. Without Dapr you would store the Payment URL, add retry loops, and handle service discovery. With Dapr you just name the other app.

public class OrderService
{
    private readonly DaprClient _dapr;
 
    public OrderService(DaprClient dapr) => _dapr = dapr;
 
    public async Task<PaymentResult> ChargeAsync(PaymentRequest request)
    {
        // "payment-service" is the Dapr app-id of the other service.
        // Dapr finds it, calls it, retries on failure, and returns the result.
        return await _dapr.InvokeMethodAsync<PaymentRequest, PaymentResult>(
            HttpMethod.Post,
            "payment-service",
            "charge",
            request);
    }
}

Notice there is no URL. You only use the app-id ("payment-service"). Dapr handles discovery, load balancing, encryption between sidecars, and automatic retries. If the Payment service moves to a new server, your code does not change.

An order service invoking a payment service through their sidecars.

Building block 2: State management

Microservices often need to remember things: a shopping cart, a user session, a draft order. Dapr gives you a simple key/value store. Behind the scenes it might be Redis, Cosmos DB, or many others. Your code stays the same.

public class CartService
{
    private readonly DaprClient _dapr;
    private const string StoreName = "statestore";
 
    public CartService(DaprClient dapr) => _dapr = dapr;
 
    public async Task SaveCartAsync(string userId, Cart cart)
    {
        // Save the cart under a key. Dapr writes it to the configured store.
        await _dapr.SaveStateAsync(StoreName, userId, cart);
    }
 
    public async Task<Cart?> GetCartAsync(string userId)
    {
        // Read it back by key. Returns null if not found.
        return await _dapr.GetStateAsync<Cart>(StoreName, userId);
    }
}

The name "statestore" points to a small YAML component file. To switch from Redis to Cosmos DB, you edit that one file. The C# code never changes. That is the magic of the standard API.

Dapr state also supports useful extras you get "for free":

FeatureWhat it gives you
Concurrency (ETags)Stops two writers from overwriting each other
Consistency optionsChoose strong or eventual consistency
Bulk operationsRead or write many keys at once
TTL (time to live)Auto-delete data after a set time

Building block 3: Publish & subscribe

Sometimes a service should not wait for an answer. When an order is placed, you may want to email the customer, update stock, and start shipping — all at once, without the order service waiting. This is where pub/sub shines. The order service publishes an event. Other services subscribe and react.

First, publishing an event:

public class OrderProcessor
{
    private readonly DaprClient _dapr;
 
    public OrderProcessor(DaprClient dapr) => _dapr = dapr;
 
    public async Task PlaceOrderAsync(Order order)
    {
        // "pubsub" is the component name. "orders" is the topic name.
        await _dapr.PublishEventAsync("pubsub", "orders", order);
        // The order service is now done. It does not wait for subscribers.
    }
}

Now, another service subscribes to that topic. In ASP.NET Core you mark an endpoint with [Topic].

[ApiController]
public class ShippingController : ControllerBase
{
    // Dapr delivers every "orders" message to this method.
    [Topic("pubsub", "orders")]
    [HttpPost("ship-order")]
    public IActionResult OnOrderPlaced(Order order)
    {
        // Start packing and shipping here.
        return Ok();
    }
}

The publisher and subscriber never know about each other. They only share a topic name. This keeps services loosely coupled, which is the whole point of good microservice design.

How a published event flows

Order
Topic
Email
Stock
Shipping

Steps

1

Order

Publishes event

2

Topic

orders queue

3

Email

Sends receipt

4

Stock

Reduces count

5

Shipping

Starts delivery

One publish, many independent reactions.
One event fans out to many subscribers without the publisher knowing them.

Building block 4: Secrets

Never put passwords or API keys directly in code or config files. Dapr gives you a standard secrets API. The real store can be a local file (for development), Azure Key Vault, HashiCorp Vault, or Kubernetes secrets.

public class ReportService
{
    private readonly DaprClient _dapr;
 
    public ReportService(DaprClient dapr) => _dapr = dapr;
 
    public async Task<string> GetDbPasswordAsync()
    {
        // Reads "db-password" from the configured secret store.
        var secret = await _dapr.GetSecretAsync("secretstore", "db-password");
        return secret["db-password"];
    }
}

Same idea as before: your code asks Dapr, and Dapr talks to the real secret store. Move from a dev file to Azure Key Vault in production by editing one component file.

How a Dapr app runs locally

When you run a Dapr app, you do not run just your program. You run your program plus its sidecar. The Dapr CLI starts both together.

// Run your service together with its Dapr sidecar:
//   dapr run --app-id order-service --app-port 5000 -- dotnet run
//
// --app-id   : the name other services use to find you
// --app-port : the port your ASP.NET Core app listens on
// the part after -- : the command that starts your app

Here is the picture of what runs on your machine.

Each service runs next to its own Dapr sidecar; sidecars share components.

Self-hosted vs Kubernetes

A common worry for beginners is: "Do I need Kubernetes?" No. Dapr runs in two main modes, and your application code is identical in both.

ModeWhere it runsWhen to use it
Self-hostedYour laptop or a single VMLearning, local development, small apps
KubernetesA real cluster in the cloudProduction, scaling, many services

You write and test on your laptop in self-hosted mode. When you are ready, you deploy the same services to Kubernetes. Dapr automatically injects the sidecar into each pod. You did not rewrite anything. This "write once, run anywhere" feeling is a big reason teams pick Dapr.

Dapr and .NET Aspire work well together

If you have read about .NET Aspire, good news: it pairs nicely with Dapr. Aspire gives you a clean local dashboard and orchestrates your services, containers, and sidecars with a single command. You get logs, traces, and metrics in one place. Dapr handles the distributed plumbing; Aspire makes running and watching everything pleasant during development. Beginners do not need both on day one, but it is worth knowing they fit together when your project grows.

A small mental model to remember

When you feel lost, return to the tiffin analogy:

  • Your app is the cook. It makes business decisions.
  • The sidecar is the delivery helper. It does the network work.
  • Building blocks are the helper's skills: deliver (invoke), store (state), broadcast (pub/sub), and fetch keys (secrets).
  • Component files say which real tool the helper uses. Swap the tool, keep the code.

If you hold this picture, every Dapr feature will make sense.

Common beginner mistakes to avoid

  • Forgetting the sidecar. If you run dotnet run alone, Dapr is not there. Always start with dapr run.
  • Hard-coding store names in many places. Put names like "statestore" in one constant or config value.
  • Treating pub/sub like a request/response call. Publishing is fire-and-forget. If you need an answer back, use service invocation instead.
  • Skipping secrets. It is tempting to paste a key into config. Use the secrets API from the start; it costs little and saves you later.

Quick recap

  • Dapr is a free, open-source runtime that handles the hard parts of microservices for you.
  • It runs as a sidecar: a helper beside your app, like a tiffin delivery helper beside a cook.
  • Your .NET app talks to Dapr through simple APIs, not directly to infrastructure.
  • The four starter building blocks are service invocation, state management, pub/sub, and secrets.
  • You swap the real tool (Redis, Kafka, Key Vault) by editing a component file, not your code.
  • Dapr runs the same way in self-hosted mode on your laptop and in Kubernetes in production.
  • Dapr stays free while some other .NET libraries have moved to paid commercial licenses.
  • Start with dapr run, inject DaprClient, and call one method at a time.

References and further reading

Related Posts