Use when building microservices with Dapr (Distributed Application Runtime) in .NET. Covers service invocation, state management, pub/sub messaging, bindings, actors, secrets, and sidecar configuration.
USE FOR: service-to-service invocation with automatic mTLS, distributed state management with pluggable stores, pub/sub messaging with topic subscriptions, output bindings to external systems, virtual actor model for per-entity stateful logic
DO NOT USE FOR: .NET Aspire orchestration without Dapr (use aspire), serverless functions (use azure-functions), simple in-process background tasks (use IHostedService), Orleans virtual actors without Dapr (use orleans)
Use when building microservices with Dapr (Distributed Application Runtime) in .NET. Covers service invocation, state management, pub/sub messaging, bindings, actors, secrets, and sidecar configuration.
USE FOR: service-to-service invocation with automatic mTLS, distributed state management with pluggable stores, pub/sub messaging with topic subscriptions, output bindings to external systems, virtual actor model for per-entity stateful logic
DO NOT USE FOR: .NET Aspire orchestration without Dapr (use aspire), serverless functions (use azure-functions), simple in-process background tasks (use IHostedService), Orleans virtual actors without Dapr (use orleans)
Dapr (Distributed Application Runtime) is a portable, event-driven runtime for building resilient microservices. It provides language-agnostic building blocks as HTTP/gRPC APIs that run alongside your application as a sidecar process. The .NET SDK (Dapr.Client, Dapr.AspNetCore) provides strongly-typed clients for service invocation, state management, pub/sub, bindings, actors, and secrets with pluggable component infrastructure configured via YAML.
NuGet Packages
dotnet add package Dapr.Client # Core DaprClient
dotnet add package Dapr.AspNetCore # ASP.NET Core integration
dotnet add package Dapr.Actors # Virtual actor model
dotnet add package Dapr.Actors.AspNetCore # Actor hosting in ASP.NET Core
dotnet add package Dapr.Extensions.Configuration # Configuration provider
Setup
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprClient();
builder.Services.AddControllers().AddDapr();
var app = builder.Build();
app.UseCloudEvents();
app.MapSubscribeHandler();
app.MapControllers();
app.Run();
Service Invocation
Call methods on other services by name without knowing their network address. Dapr handles service discovery, mTLS, retries, and load balancing.
using Dapr.Client;
publicclassOrderService(DaprClient daprClient, ILogger<OrderService> logger)
{
publicasync Task<OrderConfirmation> PlaceOrderAsync(Order order)
{
logger.LogInformation("Placing order {OrderId}", order.Id);
// Invoke a method on the "inventory-service"var available = await daprClient.InvokeMethodAsync<Order, InventoryCheck>(
appId: "inventory-service",
methodName: "check-stock",
data: order);
if (!available.InStock)
thrownew InvalidOperationException($"Item {order.ProductId} is out of stock");
// Invoke payment servicevar payment = await daprClient.InvokeMethodAsync<PaymentRequest, PaymentResult>(
appId: "payment-service",
methodName: "process",
data: new PaymentRequest(order.Id, order.Total));
returnnew OrderConfirmation(order.Id, payment.TransactionId, DateTime.UtcNow);
}
}
// HTTP verb-specific invocationpublicasync Task<Product> GetProductAsync(string productId)
{
var request = daprClient.CreateInvokeMethodRequest(
HttpMethod.Get, "catalog-service", $"products/{productId}");
returnawait daprClient.InvokeMethodAsync<Product>(request);
}
State Management
Store and retrieve state using pluggable backends (Redis, CosmosDB, PostgreSQL, etc.) configured in component YAML.
# Run with Dapr sidecar
dapr run --app-id order-service --app-port 5000 -- dotnet run
# Run multiple services
dapr run --app-id order-service --app-port 5000 --dapr-http-port 3500 -- dotnet run --project OrderService
dapr run --app-id inventory-service --app-port 5001 --dapr-http-port 3501 -- dotnet run --project InventoryService
Best Practices
Use the Dapr sidecar architecture exclusively; never embed Dapr functionality in-process because the sidecar handles mTLS, retries, and component lifecycle independently of your application code.
Inject DaprClient from DI via builder.Services.AddDaprClient() rather than constructing it with new DaprClientBuilder().Build(), to ensure consistent configuration and proper lifetime management.
Configure state stores, pub/sub brokers, and bindings via component YAML files rather than hardcoding infrastructure details in application code, enabling environment-specific configuration without code changes.
Use GetStateAndETagAsync with TrySaveStateAsync for optimistic concurrency on state operations that may conflict, rather than blind SaveStateAsync which silently overwrites concurrent changes.
Prefer the [Topic("pubsub", "topic-name")] attribute on ASP.NET controller endpoints for declarative pub/sub subscriptions rather than programmatic subscription, which is harder to discover and test.
Keep actors lightweight and focused on single-entity state management; avoid actors for bulk data processing or fan-out operations that are better suited to pub/sub or batch processing patterns.
Use Dapr's built-in resiliency policies (retries, timeouts, circuit breakers) configured via YAML rather than implementing application-level resilience with Polly, to keep resilience concerns out of application code.
Test service invocation locally with dapr run -- dotnet run and verify component configurations with dapr components -k before deploying to Kubernetes or Azure Container Apps.
Use InvokeMethodAsync<TRequest, TResponse> with strongly-typed generic parameters rather than working with raw HTTP responses, to get automatic serialization and type safety.
Store secrets in a Dapr secret store component (Azure Key Vault, HashiCorp Vault, local file) and access them via DaprClient.GetSecretAsync rather than reading environment variables directly, to centralize secret management across services.