.NET Aspire  

Add .NET Aspire to an existing .NET app

While a common misconception is that .NET Aspire is only for new projects, a core part of its design is its ability to be added to an existing .NET application. This non-invasive approach allows developers to incrementally adopt Aspire's benefits, such as unified orchestration and improved observability, without rewriting their existing codebase. The process centers on introducing a new project that acts as the control plane for your solution, simplifying the management of your existing microservices, containers, and other dependencies.

The Problem Aspire Solves for Existing Apps

Consider a typical existing distributed application. You likely have several .NET projects—a Web API, a front-end, a background worker—each with its own launchSettings.json. Additionally, your application might depend on external services like a Redis cache or a PostgreSQL database, which you currently manage manually using a separate docker-compose.yml file.

This setup presents several common pain points:

  • Manual Startup: You have to open multiple terminal windows or configure complex Visual Studio startup projects to get everything running.

  • Dependency Management: You must manually ensure that containers are running and that their connection strings and environment variables are correctly configured for each service.

  • Scattered Observability: Logs, metrics, and traces are spread across different consoles, making it difficult to debug a distributed issue.

Adding Aspire to this existing solution addresses these problems by centralizing the entire process. Instead of managing a collection of disparate services, you'll manage a single Aspire orchestrator that handles everything for you.

Step-by-Step Guide to Adding Aspire

The process of adding Aspire to an existing solution is straightforward and can be accomplished with a few simple commands.

Step 1. Install the .NET Aspire Workload

First, ensure you have the .NET Aspire workload installed on your machine. This is a prerequisite for creating and running Aspire projects.

dotnet workload install aspire

Step 2. Add the AppHost Project

The core of the Aspire orchestration model is the AppHost project. You don't directly modify your existing projects to add Aspire; instead, you add a new AppHost project that will act as the orchestrator for all your existing services.

From the root directory of your solution, use the .NET CLI to create the new project:

dotnet new aspire-apphost -o MyExistingApp.AppHost

This command creates a new directory, MyExistingApp.AppHost, containing a new project with the necessary SDKs and a Program.cs file.

Step 3. Reference Existing Projects

Next, you need to tell your new AppHost project about the existing projects it will orchestrate. You do this by adding project references, which is a key part of the Aspire workflow.

dotnet sln add MyExistingApp.AppHost/MyExistingApp.AppHost.csproj

dotnet add MyExistingApp.AppHost/MyExistingApp.AppHost.csproj reference MyExistingWebAPI/MyExistingWebAPI.csproj

dotnet add MyExistingApp.AppHost/MyExistingApp.AppHost.csproj reference MyExistingWorker/MyExistingWorker.csproj

These commands update the solution and project files, allowing the AppHost to "see" your existing services.

Step 4. Orchestrate in the AppHost's Program.cs

This is where you define your application's architecture in a clean, code-first manner. Open the Program.cs file in your new AppHost project. You'll see some basic boilerplate code. You will then use the builder.AddProject() method to add your existing projects to the orchestration model.

Let's assume your application also uses a Redis cache. Here's how you'd orchestrate it:

var builder = DistributedApplication.CreateBuilder(args);

// Add an existing web API project to the orchestration model.
var myApi = builder.AddProject<Projects.MyExistingWebAPI>("my-api");

// Add a containerized dependency (e.g., a Redis cache).
var cache = builder.AddRedisContainer("cache");

// Add an existing worker project.
var myWorker = builder.AddProject<Projects.MyExistingWorker>("my-worker")
                      .WithReference(cache); // The worker needs a reference to the cache.

// You can add multiple projects and chain their dependencies.
var myWebUI = builder.AddProject<Projects.MyExistingWebUI>("my-web-ui")
                     .WithReference(myApi); // The web UI needs to call the API.

// This builds and runs the entire orchestrated application.
builder.Build().Run();

In this code, you've declared the existence of your existing projects (MyExistingWebAPI, MyExistingWorker, etc.) and a new managed resource (Redis). The .WithReference() call is where the magic happens: Aspire automatically takes care of injecting the correct connection string for the Redis container into your MyExistingWorker project.

Step 5. Run the Application

Finally, all that's left to do is run the application. Set the MyExistingApp.AppHost project as the startup project and press F5 in Visual Studio, or run the following command from the terminal:

dotnet run --project MyExistingApp.AppHost

Aspire will now take over. It will start the Redis container, launch your existing Web API and worker projects, and automatically provide them with the necessary configuration. It also launches the Aspire Dashboard in your browser, providing a unified view of your application's health, logs, and dependencies.

apphost

What's Happening Under the Hood?

The simplicity of this process is made possible by several clever mechanisms:

  • Environment Variable Injection: Aspire doesn't modify your project's code to add dependencies. Instead, it uses a standard .NET configuration pattern. When an Aspire-managed resource is referenced by a project, Aspire automatically creates and sets the appropriate environment variables. Standard IConfiguration providers in your existing .NET app will then automatically pick these up, providing the correct connection string or service URL.

  • Service Discovery: When you add a project with builder.AddProject(), Aspire assigns it a logical name. Other projects can then use this name to find and communicate with it, eliminating the need for hard-coded URLs or port numbers.

  • Container Management: Aspire leverages your local container runtime (like Docker) to download and run the containerized dependencies you declare, such as Redis or PostgreSQL, ensuring they're always available during development.

By wrapping your existing projects with this orchestration layer, Aspire provides a consistent and repeatable development experience that is fully observable and simplifies the path to production.