General Availability of .NET Aspire: Simplifying .NET Cloud-Native Development

Microsoft made .NET Aspire generally available in May 2024, and it changes how a lot of .NET teams approach distributed application development. Aspire is not a new runtime and it does not replace ASP.NET Core. It is an opinionated stack of tooling, templates, and NuGet packages that sits on top of your existing .NET projects and coordinates them during development and, to some extent, at deployment time. If you have ever hand rolled a docker-compose.yml file just to bring up Postgres, Redis, and three microservices together for local testing, Aspire is aimed directly at that problem.

This was a genuine milestone rather than a minor preview bump. Aspire had been through several previews since it was first announced the previous November, and GA means Microsoft is committing to supported, production ready tooling rather than an experiment you use at your own risk.

Getting Aspire running

Setting up Aspire takes a few minutes on any of the three main .NET tooling paths. From the .NET CLI, run dotnet workload update followed by dotnet workload install aspire. Visual Studio 2022 17.10 bundles Aspire as a recommended component inside the ASP.NET and web development workload, so anyone who keeps Visual Studio current on that workload already has it after updating. Visual Studio Code users need the CLI workload installed first, plus the C# Dev Kit extension, which picked up Aspire support in its latest stable release around the same time as GA.

  • .NET CLI: dotnet workload update, then dotnet workload install aspire
  • Visual Studio 2022 17.10: included as a recommended component of the ASP.NET and web development workload
  • Visual Studio Code: install the Aspire workload via the CLI, then add the C# Dev Kit extension

Why this exists in the first place

The .NET team had already invested years into cloud readiness through Health Checks, YARP, the HTTP client factory, gRPC support, Native AOT, and SDK container builds. Each of those solved a specific piece of the puzzle, but building a real distributed application still meant wiring all of them together by hand, project by project. Aspire is Microsoft formalizing a pattern that a lot of experienced .NET architects were already assembling manually with Docker Compose files, environment variable juggling, and custom health check registrations.

The stated goal is to help even a single ASP.NET Core app talking to a database and a cache, not just large microservice estates. That framing matters because Aspire is genuinely useful at a smaller scale than most orchestration tooling, which is usually the point where teams decide it is not worth the setup cost.

What Aspire adds on top of a standard .NET solution: orchestration, components, tooling, and the dashboard.
What Aspire adds on top of a standard .NET solution: orchestration, components, tooling, and the dashboard.

The App Host project and the application model

Distributed applications typically consist of several projects talking to each other and to hosted services such as databases, caches, and message brokers. Managing the configuration and lifetime of all of that in the developer inner loop usually meant switching between multiple tools and scripting languages just to get a consistent local environment running.

Aspire introduces an App Host project where you describe the whole application, called an application model, in plain C#. Here is a representative Program.cs from an Aspire App Host, wiring up a Postgres database, a Redis cache, a catalog API, and a web frontend.

var builder = DistributedApplication.CreateBuilder(args);
 
var dbserver = builder.AddPostgres("dbserver")
                      .WithPgAdmin();
 
var catalogDb = dbserver.AddDatabase("catalogdb");
 
var cache = builder.AddRedis("cache")
                   .WithRedisCommander();
 
var catalogApi = builder.AddProject<Projects.AspireShop_CatalogApi>("catalogapi")
                        .WithReference(catalogDb);
 
builder.AddProject<Projects.AspireShop_WebFrontend>("webfrontend")
       .WithExternalHttpEndpoints()
       .WithReference(cache)
       .WithReference(catalogApi);
 
builder.Build().Run();

Running this file spins up containers for Postgres and Redis, starts pgAdmin and Redis Commander as management UIs, and injects the connection strings and URLs the catalog API and web frontend need to talk to everything, including to each other. If you are debugging inside Visual Studio, the debugger attaches to every project in the model automatically, not just the one you hit F5 on.

The WithReference calls are doing more work than they look like. They are what make the wiring declarative: instead of copying connection strings into appsettings.json for every environment, you declare the dependency once in the App Host and Aspire injects the right configuration value at launch time. This is the part that eliminates most of the manual docker-compose plus environment variable plumbing teams were doing before.

One detail worth internalizing early: the App Host project itself is never deployed. It has two modes, run and publish. Run drives the local inner loop experience described above. Publish produces a manifest file that statically describes the application model, meant to feed deployment tooling, but the App Host process does not run in production. Treating the App Host as a deployable artifact is a mistake I would flag early with any team adopting Aspire, because the model only exists to describe intent, not to execute it outside dev and test.

Beyond the base resource types of containers, executables, and .NET projects, Aspire ships hosting extensions for Node.js apps including common SPA frameworks, and for services like Redis, PostgreSQL, MySQL, SQL Server, Oracle, MongoDB, RabbitMQ, and NATS, along with Azure and AWS cloud resources. You can also write your own hosting extensions in C# for containers that are not covered out of the box, which keeps the model open rather than locking you into a fixed list of supported services.

The Aspire Dashboard showing orchestrated resources, endpoints, and their status once the App Host launches.
The Aspire Dashboard showing orchestrated resources, endpoints, and their status once the App Host launches.

The dashboard is for the inner loop, not for production monitoring

Once the App Host is running, a web based dashboard shows every resource in the application model along with its endpoints, environment variables, and console logs. It also surfaces OpenTelemetry data emitted by those resources: structured logs, distributed traces, and metrics, all in one place without any separate collector setup.

This is genuinely convenient for local debugging of a multi service app, since you no longer need five terminal windows open to watch five sets of logs. But the data is kept only in memory and is size limited by design. The dashboard is meant to give a near real time view of what is happening right now, not to replace an actual APM system with retention, alerting, and historical querying. Do not point a client demo or a production incident review at the Aspire dashboard expecting it to behave like Application Insights or a proper observability backend, because it was never built for that.

Azure Container Apps can also host the Aspire dashboard for applications deployed through the Azure Developer CLI, which is a reasonable middle ground if you want the same dashboard experience in a shared environment without standing up a full observability stack immediately.

Aspire components: resiliency and observability without the boilerplate

Connecting to an external database, cache, or message broker reliably usually means pulling in several NuGet packages beyond the client library itself, then writing boilerplate to wire up health checks, retries, and telemetry, and making all of it configurable per environment. Aspire Components package that boilerplate for you.

A component is a NuGet package that integrates a common client library with resiliency and observability enabled by default. It registers health checks, hooks into the application’s dependency injection and configuration system, and wires up OpenTelemetry providers for logs, traces, and metrics, along with default retry behavior where the underlying client library supports it. Aspire launched with components for PostgreSQL, Redis, SQL Server, MySQL, MongoDB, RabbitMQ, and several Azure and AWS services, all using the client libraries most .NET developers already reach for.

Aspire Components listed alongside regular NuGet packages inside Visual Studio.
Aspire Components listed alongside regular NuGet packages inside Visual Studio.

If your team is upgrading from an earlier Aspire preview, check the migration notes before assuming a straight package bump. Aspire iterated quickly across preview releases and some component APIs shifted along the way, so a project built against an early preview is unlikely to compile cleanly against the GA packages without a few adjustments.

Deploying beyond the inner loop

Aspire does not force you to change how you deploy today. Every project inside an Aspire solution is still a regular ASP.NET Core or .NET project that can be published exactly as before. What changes is that deployment tooling built to understand the App Host’s application model can now use that information to simplify the process.

The clearest example is the Azure Developer CLI, which has native support for deploying the resources described in an Aspire App Host directly to Azure Container Apps, and Visual Studio can trigger the same azd publish flow from Solution Explorer. For Kubernetes, deployment currently relies on a community built tool called Aspir8 rather than an officially maintained Microsoft path, which is worth factoring into any production readiness conversation if Kubernetes is your target platform.

Where Aspire fits, and where it does not

Aspire earns its keep on teams building genuinely distributed .NET applications, several services plus a database plus a cache plus maybe a message broker, where the local development setup has become its own maintenance burden. The declarative App Host model, the built in dashboard, and the components that wire up telemetry and retries by default remove a meaningful amount of hand written glue code.

It is a poor fit for a small application with a single API and a single database, where a plain appsettings.json and a connection string already do the job without any orchestration layer. Teams already invested in Docker Compose for local development, or in Microsoft’s earlier experimental Project Tye, should weigh the migration effort against the actual pain they are feeling today rather than adopting Aspire simply because it is new and Microsoft is promoting it heavily.

It is also worth being precise about what GA actually covers. The App Host and the local development experience are mature and genuinely production quality for the inner loop. The deployment story is strongest on Azure Container Apps through azd, reasonable but community driven on Kubernetes, and the dashboard is explicitly not a production observability tool. Evaluate each of those pieces on its own rather than treating GA as a blanket guarantee that every part of the stack is equally battle tested.

For anyone getting started, the Aspire quickstart on Microsoft Learn and the official Aspire samples repository on GitHub are the fastest way to get a working multi service app running before deciding whether to adopt it for real work.

Leave a Reply

Discover more from Behind the Stack

Subscribe now to keep reading and get access to the full archive.

Continue reading