Read time: 7 minutes
The .NET Saturday is brought to you by:
Every Aspire release brings new capabilities, but the pattern I’ve seen more and more is how the latest versions actually remove code from your projects.
And I don’t mean bug fixes or performance tweaks. I’m talking about infrastructure boilerplate that’s been cluttering your AppHost since you started using Aspire.
The Aspire team has been quietly simplifying the APIs across the last few releases.
Verbose configurations that used to take multiple lines? Now one-liners. Workarounds you needed for Azure deployments? Built into the framework.
Today, I’ll walk you through three cleanups that will simplify your application model immediately as you upgrade to the latest Aspire bits.
Let’s dive in.
Upgrading Aspire
The easiest way to upgrade Aspire is by using the Aspire CLI. So we should start by upgrading the CLI itself (using PowerShell here):
iex "& { $(irm https://aspire.dev/install.ps1) }"
This should be the last time you use that script to update your Aspire CLI. Starting with Aspire 13, the CLI includes the new –self flag for aspire update, which will let it easily self-update:
aspire update --self
Next, use aspire update to update all Aspire dependencies across all your projects in one shot:

You may also want to update your Aspire project templates:
dotnet new install Aspire.ProjectTemplates
Next, let’s start taking advantage of the new bits.
1. Use the simpler AppHost project template
The updated Aspire SDK supports a simplified project template for your AppHost. This was my Aspire 9.4 project:
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.4.1" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>92823f80-554d-4fd2-9ad2-b361574d1318</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.4.1" />
<PackageReference Include="Aspire.Hosting.Azure.AppContainers" Version="9.4.1" />
<PackageReference Include="Aspire.Hosting.Azure.ApplicationInsights" Version="9.4.1" />
<PackageReference Include="Aspire.Hosting.Azure.ServiceBus" Version="9.4.1" />
<PackageReference Include="Aspire.Hosting.Keycloak" Version="9.4.1-preview.1.25408.4" />
<PackageReference Include="Aspire.Hosting.Azure.PostgreSQL" Version="9.4.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TemplateApp.Api\TemplateApp.Api.csproj" />
<ProjectReference Include="..\TemplateApp.Worker\TemplateApp.Worker.csproj" />
</ItemGroup>
</Project>
And this is what it looks like after taking advantage of the updated template:
<Project Sdk="Aspire.AppHost.Sdk/13.0.0">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>92823f80-554d-4fd2-9ad2-b361574d1318</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.Azure.AppContainers" Version="13.0.0" />
<PackageReference Include="Aspire.Hosting.Azure.ApplicationInsights" Version="13.0.0" />
<PackageReference Include="Aspire.Hosting.Azure.ServiceBus" Version="13.0.0" />
<PackageReference Include="Aspire.Hosting.Keycloak" Version="13.0.0-preview.1.25560.3" />
<PackageReference Include="Aspire.Hosting.Azure.PostgreSQL" Version="13.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TemplateApp.Api\TemplateApp.Api.csproj" />
<ProjectReference Include="..\TemplateApp.Worker\TemplateApp.Worker.csproj" />
</ItemGroup>
</Project>
Notice how your Aspire SDK is now specified directly in your <Project> tag, including the version, and the removal of Aspire.Hosting.AppHost package, now included with the SDK.
Next, let’s start the application model clean-up.
2. Use the new Azure PostgreSQL resource properties
A few months ago, I covered how to deploy Keycloak to Azure with Aspire, which amazingly was doable with zero Bicep files, pure C#.
One of the key challenges was how to figure out the hostname of the deployed Azure PostgreSQL server, so that Keycloak can connect to it and use it as its database in the cloud.
The best we could do was this:
var postgres = builder.AddAzurePostgresFlexibleServer("postgres")
.ConfigureInfrastructure(infra =>
{
var pg = infra.GetProvisionableResources()
.OfType<PostgreSqlFlexibleServer>().Single();
infra.Add(new ProvisioningOutput("hostname", typeof(string))
{
Value = pg.FullyQualifiedDomainName
});
});
var keycloakDb = postgres.AddDatabase("keycloakDB", "keycloak");
var keycloakDbUrl = ReferenceExpression.Create(
$"jdbc:postgresql://{postgres.GetOutput("hostname")}/{keycloakDb.Resource.DatabaseName}"
);
var keycloak = builder.AddKeycloak("keycloak")
.WithEnvironment("KC_DB", "postgres")
.WithEnvironment("KC_DB_URL", keycloakDbUrl);
Notice the tricky ConfigureInfrastructure call to add the hostname output that later would be used to build the JDBC PostgreSQL connection string that Keycloak would use.
But with the latest Aspire improvements, that hostname output is now a native property on the AzurePostgresFlexibleServerResource, allowing us to reduce all that to this:
var postgres = builder.AddAzurePostgresFlexibleServer("postgres");
var keycloakDb = postgres.AddDatabase("keycloakDB", "keycloak");
var keycloakDbUrl = ReferenceExpression.Create(
$"jdbc:postgresql://{postgres.Resource.HostName}/{keycloakDb.Resource.DatabaseName}"
);
var keycloak = builder.AddKeycloak("keycloak")
.WithEnvironment("KC_DB", "postgres")
.WithEnvironment("KC_DB_URL", keycloakDbUrl);
Neat!
And in Aspire 13.1, we expect new APIs to be able to retrieve that full JDBC connection string directly from the AzurePostgresFlexibleServerResource, reducing that code much more.
Now, let’s look at our health probes.
3. Use the native HTTP health probes support
I covered the use of health checks and probes with Aspire over here, and it works really well. But, honestly, it’s a lot of code to define two simple health probes in Azure Container Apps:
var api = builder.AddProject<TemplateApp_Api>("templateapp-api")
.WithReference(templateAppDb)
.WaitFor(templateAppDb)
.WithExternalHttpEndpoints()
.PublishAsAzureContainerApp((infra, containerApp) =>
{
var container = containerApp.Template.Containers.Single().Value;
container?.Probes.Add(new ContainerAppProbe
{
ProbeType = ContainerAppProbeType.Liveness,
HttpGet = new ContainerAppHttpRequestInfo
{
Path = "/health/alive",
Port = healthPort,
Scheme = ContainerAppHttpScheme.Http
},
PeriodSeconds = 10
});
container?.Probes.Add(new ContainerAppProbe
{
ProbeType = ContainerAppProbeType.Readiness,
HttpGet = new ContainerAppHttpRequestInfo
{
Path = "/health/ready",
Port = healthPort,
Scheme = ContainerAppHttpScheme.Http
},
PeriodSeconds = 10
});
})
.WithEnvironment("HTTP_PORTS", $"8080;{healthPort.ToString()}")
.WithHttpHealthCheck("/health/ready");
With the latest Aspire updates, we can now turn all that into this:
#pragma warning disable ASPIREPROBES001
var api = builder.AddProject<TemplateApp_Api>("templateapp-api")
.WithReference(templateAppDb)
.WaitFor(templateAppDb)
.WithExternalHttpEndpoints()
.WithHttpEndpoint(
name: "health",
targetPort: 8081,
isProxied: false)
.WithHttpProbe(
ProbeType.Liveness,
"/health/alive",
periodSeconds: 10,
endpointName: "health")
.WithHttpProbe(
ProbeType.Readiness,
"/health/ready",
periodSeconds: 10,
endpointName: "health");
#pragma warning restore ASPIREPROBES001
Let’s unpack that:
- The WithHttpEndpoint call defines a new health endpoint that will listen on port 8081. It must go after WithExternalHttpEndpoints because it’s internal only, so only the health probes can reach it.
- The WithHttpProbe calls are the new way to define the probes without involving any Azure Container Apps specific syntax.
- The ASPIREPROBES001 warning must be disabled since the new API is still experimental.
And, on top of this, notice how we don’t need the separate WithHttpHealthCheck call, since WithHttpProbe will implicitly add it.
Much cleaner!
Wrapping Up
Aspire’s value isn’t just in what it adds, it’s in what it takes away.
Simpler project templates, native Azure resource properties, and cleaner APIs all point in the same direction: less ceremony, more clarity.
The best frameworks get out of your way. That’s exactly what the Aspire team keeps delivering.
Upgrade, delete some code, and enjoy the cleaner application model.
And that’s it for today.
See you next Saturday.
Whenever you’re ready, there are 4 ways I can help you:
-
.NET Backend Developer Bootcamp: A complete path from ASP.NET Core fundamentals to building, containerizing, and deploying production-ready, cloud-native apps on Azure.
-
Building Microservices With .NET: Transform the way you build .NET systems at scale.
-
Get the full source code: Download the working project from this newsletter, grab exclusive course discounts, and join a private .NET community.
-
Sponsor this newsletter: Get your product in front of 25,000+ .NET developers and tech leads who make buying decisions. Slots fill fast. Reserve today.