aspire logo

2026-06-03 @

Distributed apps with Aspire - Adding our first service

If you’re new here, make sure to check out the previous article of this series.

The Aspire structure

Last time we’ve got our Aspire app up and running. Before we continue building, let’s take a look at our starting point. Since we’ve cleaned up the errors the last time, our solution should look like this:

Aspire solution

The AppHost project

The AppHost is the orchestrator and the entry point of the whole thing. It’s the project you actually run, and starting it is what spins up the Aspire Dashboard along with everything else in your stack. It’s just a regular .NET project where you describe your services and their dependencies in code, and we’ve already poked at it in the previous article when we set ASPIRE_ALLOW_UNSECURED_TRANSPORT in its launchSettings.json.

Right now, it’s basically empty. If you open the AppHost.cs file, all you’ll find is the default scaffolding that creates the builder and runs the application, without any resources wired up yet.

var builder = DistributedApplication.CreateBuilder(args);

builder.Build().Run();

We’ll start working on it in just a moment, but first, we have to talk about the other project in our starter app.

The ServiceDefaults project

The ServiceDefaults project is a shared class library that gets referenced by some of the services. Its job is to wire up the cross-cutting concerns you’d want in most of them, like OpenTelemetry for traces and metrics, health checks and service discovery. Instead of copy-pasting that boilerplate into every project, you just call a single extension method in your service’s Program.cs and you’re set up with sensible defaults out of the box.

This is also the project we did a bit of housekeeping on in the previous article, when we updated its OpenTelemetry NuGet packages to clear up the vulnerability warnings. We won’t touch it much for now, but it’s good to know it’s there and what it does, since several services we add to the project will lean on it.

Adding a service

Since we’re using Visual Studio, adding a service to the Aspire project is really simple. Just right click the solution itself, and click Add -> New Project…

Add project menu 1

From the list of project templates, select the ASP.NET Core Web API with C# option.

Add project menu 2

Pick a name to your service. In my case, I chose AspireIntro.Blog but you can choose whatever you want.

Add project menu 3

In the last step, we only change a single thing. That being, deselecting Configure for HTTPS because we hate HTTPS here. Well, no, we don’t, but for our demo purposes, we don’t really want to think about it.

With that said, make sure to pay attention to the Enlist for Aspire orchestration option being checked. This way, Visual Studio wires our service into Aspire out of the box. We’ll take a look at what this did in a moment, just to get familiar with it, but this option is there to make your life easier, so take it.

Add project menu 4

At this point, our solution should have our new project and look like this:

Aspire solution after adding the service

What we’ve just done

So Visual Studio did some magic, but let’s demistify it.

The Blog service

The newly created AspireIntro.Blog project is just a regular ASP.NET Core web API. Here’s the Program.cs file, which we won’t really touch for a while:

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();

var app = builder.Build();

app.MapDefaultEndpoints();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast");

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

For our demo, having the single /weatherforecast endpoint is fine, at least for now. The only thing that is not standard is the second line of code in the file, specifically builder.AddServiceDefaults();. What happened here, is that VS automatically added the AspireIntro.ServiceDefaults project to the dependencies of our new service and called the AddServiceDefaults extension method defined in the Extensions.cs file.

Service dependencies

The AppHost project

The second thing VS took care of for us, is adding the new AspireIntro.Blog project to the dependencies of the AspireIntro.AppHost project.

AppHost dependencies

If you’ve paid attention to this last picture or your solution explorer, you can also see that git flagged the AppHost.cs file as changed too. Here’s what’s inside now:

var builder = DistributedApplication.CreateBuilder(args);

builder.AddProject<Projects.AspireIntro_Blog>("aspireintro-blog");

builder.Build().Run();

Now the AddProject extension method has been called on our builder. The Projects.AspireIntro_Blog is automatically generated metadata that Aspire takes care of for you, based on the dependencies of the AppHost.

Also notice, that aspireintro-blog has been passed to the name parameter of AddProject. The name you pass to your projects like this will be used by aspire for service discovery. This is one of the biggest things Aspire does for you when locally developing a distribuated application, but more about that in a later article.

The Dashboard

Resources

With everything set up, if you start your AppHost, the new aspire-blog resource should be enlisted on the dashboard.

Dashboard: Resources tab

As you can see in the URLs column, the Blog service has been started on port 5113 for me, but probably on an entirely different port in your case. If you are not familiar with ASP.NET Core, that port has been assigned on project creation and you can change it to the port of your liking by altering the applicationUrl property in the Blog project’s launchSettings.json file. In my case, I’ll alter it to 7001.

launchSettings.json

{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": false,
      "applicationUrl": "http://localhost:7001",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

With that change, after restarting the AppHost, the blog servie should be running on port 7001:

Dashboard: Resources after port change

Now, you can make a GET HTTP request to http://localhost:7001/weatherforecast however you want to and the automatically generated dummy endpoint should return back some randomly generated data, proving that our API works. I’ll use postman for this, but you can just open the URL in your browser if you wish.

/weatherforecast endpoint

Also, a quick sidenote, referencing back to ServiceDefaults. Since VS added the AddServiceDefaults() call to our service’s Program.cs, effectively registering health hecks, the /alive and /health endpoints also should automatically work now.

/health endpoint

/alive endpoint

Console

The Console tab of the dashboard gives you access to the standard output logs of your services. In our case, the only resource enlisted is aspireintro-blog, but if there were mroe, you could switch between resources in the top left corner.

Dashboard: Console tab

Notice, that the console logs contain an entirely different port. That’s because Aspire exposes our service on port 7001, as we configured it before, but internally, the app runs on a different port. Aspire takes care of the networking for you, so don’t worry about it, but you can check out those settings by clicking on the aspireintro-blog resource back on the Resources tab.

Dashboard: Resource settings

Structured

The Structured tab gives you access to your structured logs. We don’t have much here so far, but it will be interesting later, so just keep this in mind for now.

Dashboard: Structured tab

Traces && Metrics

We will take some time to talk a bit about these later, as there is not much going on here so far neither, but make sure to note to yourself that Aspire gives these to you out of the box and that is a big win.

Dashboard: Traces tab

Dashboard: Metrics tab

Next steps

Next time we’ll take care of adding a database to our Aspire application.

As always, the GitHub repo with the code for this project can be found here.

Newsletter

Sign up for my maybe monthly (not likely) newsletter for updates on new posts and other content.