Why event-driven serverless matters for modern .NET workloads
More than 70% of new cloud-native projects adopt an event-driven architecture to achieve scalability and loose coupling. For .NET developers, the challenge is to combine the familiarity of C# with a truly serverless execution model that reacts to messages, timers, or HTTP calls without managing servers.
Core components: .NET 8, Azure Container Apps, and Dapr
.NET 8 introduces native support for minimal APIs, async streams, and improved AOT compilation, which reduces cold‑start latency. Azure Container Apps offers a fully managed, Kubernetes‑based platform that scales to zero, making it ideal for serverless workloads. Dapr (Distributed Application Runtime) adds building blocks—pub/sub, state stores, bindings—through sidecars, allowing .NET code to stay clean while handling events.
System requirements and quick start checklist
Before you begin, ensure you have:
- Azure CLI 2.55+ installed.
- Docker Desktop (or a compatible container runtime).
- .NET SDK 8.0.x.
- An Azure subscription with Contributor rights.
Follow these steps to spin up a sample order‑processing service.
Step‑by‑step: Create a minimal API that publishes events via Dapr
First, scaffold a new .NET 8 web project.
dotnet new web -n OrderServiceNavigate into the folder and add the Dapr client package.
cd OrderService
dotnet add package Dapr.ClientReplace Program.cs with the following code. It defines a POST endpoint /orders that publishes an order-created event to the Dapr pub/sub component named pubsub.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
using Dapr.Client;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprClient();
var app = builder.Build();
app.MapPost("/orders", async (Order order, DaprClient dapr) =>
{
await dapr.PublishEventAsync("pubsub", "order-created", order);
return Results.Accepted();
});
app.Run();
public record Order(string Id, string Product, int Quantity);Build the container image.
dotnet publish -c Release -o out
docker build -t sarikayadev/order-service:latest . Deploy to Azure Container Apps with Dapr enabled
Create a resource group and an Azure Container Apps environment.
az group create -n dapr-demo-rg -l eastus
az containerapp env create -n dapr-env -g dapr-demo-rg --location eastusDeploy the container and turn on Dapr sidecar (version 1.12 is stable as of 2024).
az containerapp create \
--name order-service \
--resource-group dapr-demo-rg \
--environment dapr-env \
--image sarikayadev/order-service:latest \
--ingress external \
--target-port 80 \
--dapr-enabled true \
--dapr-app-id order-service \
--dapr-app-port 80 \
--dapr-config "dapr-config"The --dapr-enabled true flag injects the Dapr sidecar, which automatically reads the components folder you will upload next.
Configure a pub/sub component (Azure Service Bus) for Dapr
Create a Service Bus namespace (standard tier) and a topic called orders. Grab the connection string from the Azure portal.
az servicebus namespace create -g dapr-demo-rg -n orderbus --sku Standard
az servicebus topic create -g dapr-demo-rg --namespace-name orderbus -n ordersSave the following YAML as pubsub.yaml and upload it to the Dapr components folder of the Container App.
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
namespace: default
spec:
type: pubsub.azure.servicebus
version: v1
metadata:
- name: connectionString
value: "Endpoint=sb://orderbus.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=YOUR_KEY"
- name: consumerID
value: "order-service"Upload with:
az containerapp env dapr component set \
--name dapr-env \
--resource-group dapr-demo-rg \
--component-file pubsub.yaml Consume the event with a second microservice
Build a simple subscriber that logs every order. The Dapr sidecar will invoke the endpoint automatically.
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
using Dapr;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapSubscribeHandler();
app.MapPost("/order-created", (Order order) =>
{
Console.WriteLine($"Received order {order.Id} for {order.Product}");
return Results.Ok();
});
app.Run();
public record Order(string Id, string Product, int Quantity);Deploy this service the same way, giving it a distinct --dapr-app-id subscriber. Dapr will route order-created events to /order-created of the subscriber automatically.
Practical tips for production readiness
- Cold‑start mitigation: Enable .NET 8 native AOT for the container image; it can shave 30‑40% off first‑request latency.
- Observability: Turn on Dapr metrics and send them to Azure Monitor. Add
--enable-dapr-metrics truein the Container App definition. - Retry policies: Configure Dapr pub/sub with
deadLetterTopicandmaxDeliveryCountto avoid message loss. - Security: Store Service Bus connection strings in Azure Key Vault and reference them via
secretRefin the component YAML.
Conclusion
By pairing .NET 8’s lightweight APIs with Azure Container Apps’ serverless scaling and Dapr’s event‑driven building blocks, you can construct microservices that start at zero, react instantly to events, and stay maintainable. The approach eliminates the need for custom orchestration code, lets you focus on business logic, and leverages Azure’s managed infrastructure for reliability.
Sources
- Microsoft Docs – Azure Container Apps
- Dapr.io – Pub/Sub building block
- .NET Blog – .NET 8 release notes
Author: Mahmut Sarıkaya — sarikayadev.com