INDUSTRY NEWS

Aspire 13.2: What .NET Service Devs Need to Know

Sharing some notes on the Aspire 13.2 release from the engineering team here at Iron Software. We ship .NET libraries (IronPDF, IronOCR, IronXL, IronWord, IronBarcode, and the rest), and pretty much every customer call ends up touching distributed app orchestration. That's why we pay attention to Aspire releases. 13.2 is the first one where the CLI feels like it can actually replace the dashboard for most day-to-day work, and there are a few breaking changes that will bite you on upgrade.

This isn't a release-notes regurgitation. The official what's-new page has the exhaustive list. This is the stuff that's genuinely useful in a working codebase, plus the gotchas to watch for.

TL;DR

  • CLI is now genuinely scriptable: detached mode, aspire ps, aspire stop, isolated mode, JSON output
  • TypeScript AppHost is in preview if you want to ditch the .csproj for the orchestration layer
  • Config files consolidated to aspire.config.json (legacy files auto-migrate)
  • Foundry replaces Azure AI Foundry. This one will break your build
  • WithSecretBuildArg is renamed to WithBuildSecret
  • Service discovery env vars now use scheme, not endpoint name (silent break risk)
  • Default Azure credential behavior changed in client integrations

The CLI is finally a CLI

This is the headline. Pre-13.2, aspire run blocked your terminal and the dashboard was the only realistic surface for managing a running apphost. Fine for solo dev, awkward for CI, integration tests, or any agent-driven workflow.

13.2 fixes this:

# Run in the background
aspire run --detach

# Or the new shortcut
aspire start

# See what's running
aspire ps

# Stop it
aspire stop
aspire stop --all
# Run in the background
aspire run --detach

# Or the new shortcut
aspire start

# See what's running
aspire ps

# Stop it
aspire stop
aspire stop --all
SHELL

Combined with --format json (which goes to stdout while status messages go to stderr, important if you're piping anywhere), you can build real automation around this. aspire ps --resources --format json is a solid building block for editor integrations and scripts.

Isolated mode is the unsung hero

--isolated is the one we've been waiting for. It runs an apphost with randomized ports and isolated user secrets, preventing port conflicts and configuration collisions:

aspire run --isolated
aspire start --isolated
aspire run --isolated
aspire start --isolated
SHELL

If you've ever tried to run two checkouts of the same apphost simultaneously — say main against a feature branch, parallel integration tests, or agent-driven workflows — you've felt the pain. Random ports plus separated secrets means you can finally just spin up N copies and not care.

For git worktrees alone, this is worth the upgrade. For integration test suites that bring up real services with native dependencies (Chrome rendering for PDF generation, Tesseract for OCR, the usual heavyweights), it's the difference between flaky and reliable.

aspire doctor and aspire describe

aspire doctor runs through your environment: dev cert state, container runtime version, .NET SDK, WSL2 config, agent configuration. It's the kind of thing every framework should have and most don't bother with. Output is actionable. When something's wrong, it tells you what to do.

aspire describe --follow gives you a streaming view of resource state from the terminal. Same data the dashboard shows, but pipeable. Drop it into a tmux pane and you get most of the dashboard's value in 80 columns.

Resource commands got tidier

The old resource-start / resource-stop / resource-restart commands are deprecated in favor of the cleaner subcommand form:

aspire resource api restart
aspire resource api rebuild
aspire resource api restart
aspire resource api rebuild
SHELL

rebuild is new. It stops, builds, and restarts a single .NET project resource without tearing down the whole apphost session. If you've ever changed one service in a 12-resource graph and groaned at restarting everything, this is the fix. We've felt this one ourselves: when you're iterating on a PDF render template or tweaking OCR preprocessing, restarting the whole graph just to reload one project gets old fast.

Secrets and certs without leaving the CLI

Two new dedicated command groups:

aspire certs clean
aspire certs trust

aspire secret set ApiKey super-secret-value
aspire secret list --format json
aspire certs clean
aspire certs trust

aspire secret set ApiKey super-secret-value
aspire secret list --format json
SHELL

aspire secret is the bigger win. It maps to the same user-secrets store that backs AddParameter(..., secret: true) in the app model, but you don't need the .NET CLI installed to manage them. In a polyglot apphost where not every dev has the .NET SDK, this matters.

aspire wait for CI

aspire wait api --status healthy --timeout 120
aspire wait api --status healthy --timeout 120
SHELL

Block on a resource state. Combined with aspire start and --format json, you can finally write CI scripts that wait for things to actually be ready instead of sleep 30 && hope.

Configuration: one file to rule them all

Aspire is consolidating its config files. The old split between .aspire/settings.json and apphost.run.json is gone, replaced by a single aspire.config.json at the project root:

{
  "appHost": {
    "path": "apphost.ts",
    "language": "typescript/nodejs"
  },
  "sdk": { "version": "13.2.0" },
  "channel": "stable",
  "profiles": {
    "default": {
      "applicationUrl": "https://localhost:17000;http://localhost:15000"
    }
  }
}

Migration is automatic. The first time you run any aspire command in an existing project, the legacy files get merged into the new format with paths re-based to the project root. Legacy files are preserved so you can still use older CLI versions side by side. Global settings (globalsettings.json) get migrated too.

If you have automation that pokes at .aspire/settings.json or apphost.run.json directly, plan to move it.

TypeScript AppHost (preview)

This is interesting even if you're not going to use it on day one. You can now write your apphost in TypeScript instead of C#:

import { createBuilder } from './.modules/aspire.js';

const builder = await createBuilder();

const cache = await builder.addRedis("cache");

const api = await builder.addProject("api", "../api")
    .withReference(cache)
    .waitFor(cache);

await builder.build().run();

Under the hood, the TS apphost runs as a guest process talking to Aspire's .NET orchestration host over JSON-RPC on a local transport. Same resource model, same dashboard, same integrations, just expressed in TypeScript.

The interesting bit is the codegen. When you run aspire add, the CLI inspects the integration's .NET assembly and generates a TypeScript SDK into .modules/. aspire restore regenerates them, useful after upgrading or switching branches (also runs automatically on aspire run). The 13.2 generator also added Go, Java, and Rust test targets, which hints at where this is heading.

For .NET-first teams like ours, this is more "watch this" than "ship this," but the codegen pattern means future polyglot apphost languages all follow the same model. See the multi-language architecture docs for how the host bridge works.

Dashboard: telemetry export/import is the new toy

The dashboard got a real export/import workflow. From Settings → Manage, pick resources and telemetry types and export them as JSON in a zip. Reimport into the dashboard later, or hand it off to someone else (or an LLM) for analysis.

The aspire export CLI command produces the same bundle:

aspire export --output .\artifacts\aspire-export.zip
aspire export <resource>
aspire export --output .\artifacts\aspire-export.zip
aspire export <resource>
SHELL

Genuinely useful for bug reports. Instead of "here are some screenshots and a log file," you can attach a snapshot of the actual telemetry state.

Other dashboard-side notes:

  • You can set resource parameters directly from the dashboard UI now, with an option to persist to user secrets
  • Environment variables can be exported as .env files from the resource details view
  • The resource graph layout uses adaptive force-directed positioning. Complex graphs are noticeably less cluttered
  • Telemetry HTTP API at /api/telemetry returns OTLP JSON; supports ?follow=true for NDJSON streaming. Endpoints cover resources, spans, logs, and traces (including /traces/{traceId} for full trace lookup)

The standalone dashboard now defaults the telemetry API to off. If you're hosting the dashboard yourself and depending on the API, you need DASHBOARD__API__ENABLED=true plus auth config (DASHBOARD__API__AUTHMODE and DASHBOARD__API__PRIMARYAPIKEY). AppHost-integrated scenarios still work because Aspire.Hosting wires the API up automatically for tooling.

App model bits worth noting

WithMcpServer

You can declare in the app model that a resource hosts an MCP endpoint:

var api = builder.AddProject<Projects.MyApi>("api")
    .WithMcpServer("/mcp");
var api = builder.AddProject<Projects.MyApi>("api")
    .WithMcpServer("/mcp");
Dim api = builder.AddProject(Of Projects.MyApi)("api") _
    .WithMcpServer("/mcp")
$vbLabelText   $csharpLabel

Aspire tooling can then discover and proxy that endpoint. If you're shipping anything that exposes tools to coding agents, this is the cleanest way to wire it in. Custom path or endpoint name supported via options.

Contextual endpoint resolution

This is the kind of thing you don't notice until you need it. Endpoints can now be resolved from the perspective of a specific caller or network:

var endpoint = redis.GetEndpoint("tcp");

var url = await endpoint.GetValueAsync(new ValueProviderContext {
    Caller = containerApp.Resource,
});
var endpoint = redis.GetEndpoint("tcp");

var url = await endpoint.GetValueAsync(new ValueProviderContext {
    Caller = containerApp.Resource,
});
Dim endpoint = redis.GetEndpoint("tcp")

Dim url = Await endpoint.GetValueAsync(New ValueProviderContext With {
    .Caller = containerApp.Resource
})
$vbLabelText   $csharpLabel

The same Redis endpoint will resolve to localhost:6379 from a host process, host.docker.internal:6379 from a container on the host network, or cache:6379 from a container on the Aspire container network, depending on context. The KnownNetworkIdentifiers class gives you LocalhostNetwork, DefaultAspireContainerNetwork, and PublicInternet if you'd rather pick a network than a caller.

The release notes explicitly call out that these APIs existed in 13.1 but didn't behave correctly. So if you wrote anything against them in 13.1, retest. Full details in the resource hierarchies docs.

Container build secrets

WithSecretBuildArg is renamed to WithBuildSecret. The new name is clearer. These flow through Docker/Podman as proper build secrets, not as build args (which leak into image history).

builder.AddContainer("worker", "contoso/worker")
    .WithDockerfile("../worker")
    .WithBuildSecret("ACCESS_TOKEN", accessToken);
builder.AddContainer("worker", "contoso/worker")
    .WithDockerfile("../worker")
    .WithBuildSecret("ACCESS_TOKEN", accessToken);
$vbLabelText   $csharpLabel

Build secrets can now also be files (e.g., .npmrc for private registry auth in container builds), which covers most of the real-world use cases.

Integrations: the ones that matter

The full list is long. These are the ones I'd flag:

  • Docker Compose publishing is now stable (was prerelease). AddDockerComposeEnvironment generates a docker-compose.yaml from your app model at publish time. Useful escape hatch when "deploy to Azure" isn't the answer. Worth noting if you're shipping containers that include native dependencies, since IronPDF, IronOCR, and IronXL all support Linux containers and Docker cleanly, so the generated compose file usually works without manual surgery.
  • Azure Virtual Network integration (Aspire.Hosting.Azure.Network) lets you declare VNets, subnets, NSGs, NAT gateways, and private endpoints in the apphost. AddPrivateEndpoint automatically creates private DNS zones, virtual network links, and disables public access on the target. This is the kind of thing that previously meant maintaining a separate Bicep file.
  • Azure Data Lake Storage got both hosting and client support: AddDataLake, AddDataLakeFileSystem, plus AddAzureDataLakeServiceClient / AddAzureDataLakeFileSystemClient on the client side. DI registration, retries, health checks, telemetry, the usual Aspire stack.
  • MongoDB EF Core has a new client integration (Aspire.MongoDB.EntityFrameworkCore). AddMongoDbContext<TContext> for the typical case, or EnrichMongoDbContext<TContext>() if you're registering the DbContext yourself.
  • Azure AI Inference now supports embeddings, not just chat. Register AddAzureEmbeddingsClient("embeddings").AddEmbeddingGenerator() and inject IEmbeddingGenerator<string, Embedding<float>>. Keyed variant available too.
  • Azure Container Registry got WithPurgeTask("0 1 * * *", ago: TimeSpan.FromDays(7), keep: 5), which provisions an ACR purge task on a cron schedule.
  • Bun support for JavaScript resources via WithBun(). Yarn reliability with AddViteApp was also fixed via WithYarn().
  • Microsoft Foundry replaces Azure AI Foundry. Aspire.Hosting.Foundry replaces Aspire.Hosting.Azure.AIFoundry. Breaking change; details below.

Putting it together: a document service in Aspire 13.2

Here's the pattern we run internally for testing distributed scenarios with our libraries. It's worth showing because most of the new 13.2 features pay off in this kind of multi-service setup, not in toy demos.

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");

// A worker service that uses IronPDF for HTML to PDF rendering
var renderer = builder.AddProject<Projects.PdfRenderer>("renderer")
    .WithReference(cache)
    .WaitFor(cache)
    .WithMcpServer("/mcp");

// An OCR worker that uses IronOCR for image and PDF text extraction
var ocr = builder.AddProject<Projects.OcrWorker>("ocr-worker")
    .WithReference(cache);

// API gateway that fans out to both
builder.AddProject<Projects.Api>("api")
    .WithReference(renderer)
    .WithReference(ocr)
    .WaitFor(renderer)
    .WaitFor(ocr);

builder.Build().Run();
var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("cache");

// A worker service that uses IronPDF for HTML to PDF rendering
var renderer = builder.AddProject<Projects.PdfRenderer>("renderer")
    .WithReference(cache)
    .WaitFor(cache)
    .WithMcpServer("/mcp");

// An OCR worker that uses IronOCR for image and PDF text extraction
var ocr = builder.AddProject<Projects.OcrWorker>("ocr-worker")
    .WithReference(cache);

// API gateway that fans out to both
builder.AddProject<Projects.Api>("api")
    .WithReference(renderer)
    .WithReference(ocr)
    .WaitFor(renderer)
    .WaitFor(ocr);

builder.Build().Run();
Imports DistributedApplication

Dim builder = DistributedApplication.CreateBuilder(args)

Dim cache = builder.AddRedis("cache")

' A worker service that uses IronPDF for HTML to PDF rendering
Dim renderer = builder.AddProject(Of Projects.PdfRenderer)("renderer") _
    .WithReference(cache) _
    .WaitFor(cache) _
    .WithMcpServer("/mcp")

' An OCR worker that uses IronOCR for image and PDF text extraction
Dim ocr = builder.AddProject(Of Projects.OcrWorker)("ocr-worker") _
    .WithReference(cache)

' API gateway that fans out to both
builder.AddProject(Of Projects.Api)("api") _
    .WithReference(renderer) _
    .WithReference(ocr) _
    .WaitFor(renderer) _
    .WaitFor(ocr)

builder.Build().Run()
$vbLabelText   $csharpLabel

What you get from 13.2 specifically:

  • aspire start --isolated lets you run two copies of this graph side by side without port collisions. Useful when comparing branches or running parallel integration tests against the renderer
  • aspire resource renderer rebuild reloads just the PDF renderer when you change a Razor template, instead of bouncing the whole graph
  • aspire wait renderer --status healthy --timeout 120 lets your CI block until Chrome rendering is initialized before running PDF generation tests
  • The telemetry HTTP API and aspire export give you OTLP-formatted spans for every render call, which is how you'd actually catch a slow CSS rule in production traffic
  • WithMcpServer lets you expose the renderer as an MCP tool for coding-agent workflows, useful if you're building anything that programmatically generates docs

If you want to build a service like the renderer above, IronPDF's HTML to PDF tutorial walks through the C# side. For the OCR worker, the IronOCR getting-started guide covers the basics.

Breaking changes that will actually bite you

In rough order of how likely you are to hit them:

Service discovery env var naming

# Before (13.0/13.1)
services__myservice__myendpoint__0 = https://localhost:5001

# After (13.2)
services__myservice__https__0 = https://localhost:5001
# Before (13.0/13.1)
services__myservice__myendpoint__0 = https://localhost:5001

# After (13.2)
services__myservice__https__0 = https://localhost:5001
SHELL

The endpoint scheme is used instead of the endpoint name. If you have any code or config matching on those env var names, update it. This is the most likely silent break: nothing throws, the variables just have different keys.

BeforeResourceStartedEvent

Previously fired more broadly; now only fires when actually starting a resource, not on every state change. If your handler counted on the previous behavior, it'll quietly stop running.

AIFoundry to Foundry

Package and API rename. Update the package reference and the calls:

<PackageReference Include="Aspire.Hosting.Foundry" Version="13.2.0" />
<PackageReference Include="Aspire.Hosting.Foundry" Version="13.2.0" />
XML
// Before
var ai = builder.AddAzureAIFoundry("ai");

// After
var foundry = builder.AddFoundry("ai");
var project = foundry.AddProject("agents");
var chat = project.AddModelDeployment("chat", FoundryModel.OpenAI.Gpt5Mini);
// Before
var ai = builder.AddAzureAIFoundry("ai");

// After
var foundry = builder.AddFoundry("ai");
var project = foundry.AddProject("agents");
var chat = project.AddModelDeployment("chat", FoundryModel.OpenAI.Gpt5Mini);
' Before
Dim ai = builder.AddAzureAIFoundry("ai")

' After
Dim foundry = builder.AddFoundry("ai")
Dim project = foundry.AddProject("agents")
Dim chat = project.AddModelDeployment("chat", FoundryModel.OpenAI.Gpt5Mini)
$vbLabelText   $csharpLabel

RunAsFoundryLocal still works for local model dev, but Foundry Projects aren't supported when the parent resource is configured as Foundry Local.

Default Azure credential

Aspire Azure client integrations no longer use the parameterless DefaultAzureCredential constructor. If you were relying on credentials other than ManagedIdentityCredential working in an Azure service, behavior changes. Read the Default Azure credential doc before upgrading prod.

Resource command rename

resource-start / resource-stop / resource-restart are now aspire resource <name> start|stop|restart. Update any scripts. The --apphost option is also now preferred over the legacy --project (which is still accepted).

Connection property suffix

A connection property suffix was added. If you access connection properties directly (rather than via WithReference), check your code still resolves them.

WithSecretBuildArg to WithBuildSecret

Mentioned above. Straight rename.

IAzureContainerRegistry obsolete

Use the ContainerRegistry property on compute environments instead.

Dashboard telemetry API now opt-in (standalone)

Already covered above, but worth repeating: standalone dashboard deployments need to explicitly enable the API now.

Should you upgrade?

For a working .NET shop running multi-service apps locally, yes, assuming you've inventoried the breaking changes above. The CLI improvements alone make it worth it. Detached mode and isolated mode in particular fix actual workflow problems.

For Foundry users: the rename is a forced migration if you want anything new from this release, so plan accordingly.

For TypeScript-curious folks: 13.2 is the first release where the TS apphost is real enough to evaluate. Still preview, but worth a Friday afternoon.

The upgrade itself is a one-liner if you're already on 13.x:

aspire update --self
aspire update
aspire update --self
aspire update
SHELL

If you're on 12.x or earlier, hit the upgrade guide first. There's a 13.0 step you can't skip.

Patch note: 13.2.1

13.2.1 has shipped since the original release with reliability fixes. There's a small TypeScript SDK rename worth noting, only matters if you're already on the TS apphost preview:

PreviousNew
runAsExistingFromParameters(name, resourceGroup)runAsExisting(name, { resourceGroup })
publishAsExistingFromParameters(name, resourceGroup)publishAsExisting(name, { resourceGroup })
withConnectionPropertyValue(name, value)withConnectionProperty(name, value)
withParameterBuildArg(name, parameter)withBuildArg(name, parameter)

withConnectionPropertyValue is kept as a compatibility alias in the generated SDKs, so it's not a runtime break.

Building distributed .NET apps with document workloads?

If your services do PDF generation, OCR, Excel processing, barcodes, or any of the other formats we cover, our libraries are designed for exactly the kind of multi-service, container-friendly setup Aspire orchestrates. Everything supports .NET 10, 9, 8, 7, 6, Framework, and Core, and runs in Linux containers, Azure, AWS, and on-prem.

A few places to start:

  • IronPDF for HTML to PDF, PDF editing, signing, and forms. The tutorials hub is the fastest path to a working render service
  • IronOCR for image and PDF text extraction in 125+ languages
  • IronXL for Excel read/write without Office Interop
  • IronWord for DOCX generation and editing
  • IronBarcode and IronQR for barcode and QR generation and scanning
  • Iron Suite if you need more than one of the above

You can grab a 30-day trial key and have a working PDF or OCR service running inside an Aspire apphost in well under an hour. If you hit anything weird, our support team is actual engineers, not a ticket triage queue.

That's it. Catch you on the next release.