Skip to content

.NET Aspire release 9.4 content #3926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/app-host/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ For more information, see [.NET Aspire and launch profiles](../fundamentals/laun
|--|--|--|
| `ASPIRE_ALLOW_UNSECURED_TRANSPORT` | `false` | Allows communication with the app host without https. `ASPNETCORE_URLS` (dashboard address) and `ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL` (app host resource service address) must be secured with HTTPS unless true. |
| `ASPIRE_CONTAINER_RUNTIME` | `docker` | Allows the user of alternative container runtimes for resources backed by containers. Possible values are `docker` (default) or `podman`. See [Setup and tooling overview for more details](../fundamentals/setup-tooling.md). |
| `ASPIRE_VERSION_CHECK_DISABLED` | `false` | When set to `true`, .NET Aspire doesn't check for newer versions on startup. |

## Resource service

Expand Down
37 changes: 22 additions & 15 deletions docs/app-host/eventing.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Eventing in .NET Aspire
description: Learn how to use the .NET eventing features with .NET Aspire.
ms.date: 04/04/2025
ms.date: 07/10/2025
---

# Eventing in .NET Aspire
Expand All @@ -17,48 +17,55 @@ In this article, you learn how to use the eventing features in .NET Aspire.
The following events are available in the app host and occur in the following order:

1. <xref:Aspire.Hosting.ApplicationModel.BeforeStartEvent>: This event is raised before the app host starts.
1. <xref:Aspire.Hosting.ApplicationModel.AfterEndpointsAllocatedEvent>: This event is raised after the app host allocated endpoints.
1. <xref:Aspire.Hosting.ApplicationModel.ResourceEndpointsAllocatedEvent>: This event is raised per resource after its endpoints are allocated.
1. <xref:Aspire.Hosting.ApplicationModel.AfterResourcesCreatedEvent>: This event is raised after the app host created resources.

All of the preceding events are analogous to the [app host life cycles](xref:dotnet/aspire/app-host#app-host-life-cycles). That is, an implementation of the <xref:Aspire.Hosting.Lifecycle.IDistributedApplicationLifecycleHook> could handle these events just the same. With the eventing API, however, you can run arbitrary code when these events are raised and event define custom eventsβ€”any event that implements the <xref:Aspire.Hosting.Eventing.IDistributedApplicationEvent> interface.

### Subscribe to app host events

To subscribe to the built-in app host events, use the eventing API. After you have a distributed application builder instance, walk up to the <xref:Aspire.Hosting.IDistributedApplicationBuilder.Eventing?displayProperty=nameWithType> property and call the <xref:Aspire.Hosting.Eventing.IDistributedApplicationEventing.Subscribe``1(System.Func{``0,System.Threading.CancellationToken,System.Threading.Tasks.Task})> API. Consider the following sample app host _Program.cs_ file:
To subscribe to the built-in app host events, use the eventing API. After you have a distributed application builder instance, walk up to the <xref:Aspire.Hosting.IDistributedApplicationBuilder.Eventing?displayProperty=nameWithType> property and call the <xref:Aspire.Hosting.Eventing.IDistributedApplicationEventing.Subscribe``1(System.Func{``0,System.Threading.CancellationToken,System.Threading.Tasks.Task})> API. Consider the following sample app host _AppHost.cs_ file:

:::code source="snippets/AspireApp/AspireApp.AppHost/Program.cs":::
:::code source="snippets/AspireApp/AspireApp.AppHost/AppHost.cs":::

The preceding code is based on the starter template with the addition of the calls to the `Subscribe` API. The `Subscribe<T>` API returns a <xref:Aspire.Hosting.Eventing.DistributedApplicationEventSubscription> instance that you can use to unsubscribe from the event. It's common to discard the returned subscriptions, as you don't usually need to unsubscribe from events as the entire app is torn down when the app host is shut down.

When the app host is run, by the time the .NET Aspire dashboard is displayed, you should see the following log output in the console:

:::code language="Plaintext" source="snippets/AspireApp/AspireApp.AppHost/Console.txt" highlight="2,10-14,20":::
:::code language="Plaintext" source="snippets/AspireApp/AspireApp.AppHost/Console.txt" highlight="2,10,12,14,16,22":::

The log output confirms that event handlers are executed in the order of the app host life cycle events. The subscription order doesn't affect execution order. The `BeforeStartEvent` is triggered first, followed by `AfterEndpointsAllocatedEvent`, then each resource has its `ResourceEndpointsAllocatedEvent` event fired, and finally `AfterResourcesCreatedEvent`.
The log output confirms that event handlers are executed in the order of the app host life cycle events. The subscription order doesn't affect execution order. The `BeforeStartEvent` is triggered first, followed by each resource's `ResourceEndpointsAllocatedEvent`, and finally `AfterResourcesCreatedEvent`.

## Resource eventing

In addition to the app host events, you can also subscribe to resource events. Resource events are raised specific to an individual resource. Resource events are defined as implementations of the <xref:Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent> interface. The following resource events are available in the listed order:

1. `InitializeResourceEvent`: Raised by orchestrators to signal to resources that they should initialize themselves.
1. <xref:Aspire.Hosting.ApplicationModel.InitializeResourceEvent>: Raised by orchestrators to signal to resources that they should initialize themselves.
1. <xref:Aspire.Hosting.ApplicationModel.ResourceEndpointsAllocatedEvent>: Raised when the orchestrator allocates endpoints for a resource.
1. <xref:Aspire.Hosting.ApplicationModel.ConnectionStringAvailableEvent>: Raised when a connection string becomes available for a resource.
1. <xref:Aspire.Hosting.ApplicationModel.BeforeResourceStartedEvent>: Raised before the orchestrator starts a new resource.
1. <xref:Aspire.Hosting.ApplicationModel.ResourceReadyEvent>: Raised when a resource initially transitions to a ready state.

### Subscribe to resource events

To subscribe to resource events, use the eventing API. After you have a distributed application builder instance, walk up to the <xref:Aspire.Hosting.IDistributedApplicationBuilder.Eventing?displayProperty=nameWithType> property and call the <xref:Aspire.Hosting.Eventing.IDistributedApplicationEventing.Subscribe``1(Aspire.Hosting.ApplicationModel.IResource,System.Func{``0,System.Threading.CancellationToken,System.Threading.Tasks.Task})> API. Consider the following sample app host _Program.cs_ file:
To subscribe to resource events, use the convenience-based extension methodsβ€”`On*`. After you have a distributed application builder instance, and a resource builder, walk up to the instance and chain a call to the desired `On*` event API. Consider the following sample app host _AppHost.cs_ file:

:::code source="snippets/AspireApp/AspireApp.ResourceAppHost/Program.cs":::
:::code source="snippets/AspireApp/AspireApp.ResourceAppHost/AppHost.cs":::

The preceding code subscribes to the `InitializeResourceEvent`, `ResourceReadyEvent`, `ConnectionStringAvailableEvent`, and `BeforeResourceStartedEvent` events on the `cache` resource. When <xref:Aspire.Hosting.RedisBuilderExtensions.AddRedis*> is called, it returns an <xref:Aspire.Hosting.ApplicationModel.IResourceBuilder`1> where `T` is a <xref:Aspire.Hosting.ApplicationModel.RedisResource>. The resource builder exposes the resource as the <xref:Aspire.Hosting.ApplicationModel.IResourceBuilder`1.Resource?displayProperty=nameWithType> property. The resource in question is then passed to the `Subscribe` API to subscribe to the events on the resource.
The preceding code subscribes to the `InitializeResourceEvent`, `ResourceReadyEvent`, `ResourceEndpointsAllocatedEvent`, `ConnectionStringAvailableEvent`, and `BeforeResourceStartedEvent` events on the `cache` resource. When <xref:Aspire.Hosting.RedisBuilderExtensions.AddRedis*> is called, it returns an <xref:Aspire.Hosting.ApplicationModel.IResourceBuilder`1> where `T` is a <xref:Aspire.Hosting.ApplicationModel.RedisResource>. Chain calls to the `On*` methods to subscribe to the events. The `On*` methods return the same <xref:Aspire.Hosting.ApplicationModel.IResourceBuilder`1> instance, so you can chain multiple calls:

- `OnInitializeResource`: Subscribes to the <xref:Aspire.Hosting.ApplicationModel.InitializeResourceEvent> event.
- `OnResourceEndpointsAllocated`: Subscribes to the <xref:Aspire.Hosting.ApplicationModel.ResourceEndpointsAllocatedEvent> event.
- `OnConnectionStringAvailable`: Subscribes to the <xref:Aspire.Hosting.ApplicationModel.ConnectionStringAvailableEvent> event.
- `OnBeforeResourceStarted`: Subscribes to the <xref:Aspire.Hosting.ApplicationModel.BeforeResourceStartedEvent> event.
- `OnResourceReady`: Subscribes to the <xref:Aspire.Hosting.ApplicationModel.ResourceReadyEvent> event.

When the app host is run, by the time the .NET Aspire dashboard is displayed, you should see the following log output in the console:

:::code language="Plaintext" source="snippets/AspireApp/AspireApp.ResourceAppHost/Console.txt" highlight="8,10,12,18":::
:::code language="Plaintext" source="snippets/AspireApp/AspireApp.ResourceAppHost/Console.txt" highlight="8,10,12,14,20":::

> [!NOTE]
> Some events are blocking. For example, when the `BeforeResourceStartEvent` is published, the startup of the resource will be blocked until all subscriptions for that event on a given resource have completed executing. Whether an event is blocking or not depends on how it is published (see the following section).
> Some events block execution. For example, when the `BeforeResourceStartedEvent` is published, the resource startup blocks until all subscriptions for that event on a given resource finish executing. Whether an event blocks or not depends on how you publish it (see the following section).

## Publish events

Expand All @@ -74,15 +81,15 @@ Then, you can subscribe and publish the event by calling the either of the follo
When events are dispatched, you can control how the events are dispatched to subscribers. The event dispatch behavior is specified with the `EventDispatchBehavior` enum. The following behaviors are available:

- <xref:Aspire.Hosting.Eventing.EventDispatchBehavior.BlockingSequential?displayProperty=nameWithType>: Fires events sequentially and blocks until they're all processed.
- <xref:Aspire.Hosting.Eventing.EventDispatchBehavior.BlockingConcurrent?displayProperty=nameWithType>: Fires events concurrently and blocks until they are all processed.
- <xref:Aspire.Hosting.Eventing.EventDispatchBehavior.BlockingConcurrent?displayProperty=nameWithType>: Fires events concurrently and blocks until they're all processed.
- <xref:Aspire.Hosting.Eventing.EventDispatchBehavior.NonBlockingSequential?displayProperty=nameWithType>: Fires events sequentially but doesn't block.
- <xref:Aspire.Hosting.Eventing.EventDispatchBehavior.NonBlockingConcurrent?displayProperty=nameWithType>: Fires events concurrently but doesn't block.

The default behavior is `EventDispatchBehavior.BlockingSequential`. To override this behavior, when calling a publishing API such as <xref:Aspire.Hosting.Eventing.IDistributedApplicationEventing.PublishAsync*>, provide the desired behavior as an argument.

## App Host life cycle events

As you have seen, eventing is the most flexible approach. However, in this section you learn about the alternative: life cycle events.
Eventing offers the most flexibility. However, this section explains the alternative: life cycle events.

The .NET Aspire app host exposes several life cycles that you can hook into by implementing the <xref:Aspire.Hosting.Lifecycle.IDistributedApplicationLifecycleHook> interface. The following lifecycle methods are available:

Expand All @@ -96,7 +103,7 @@ The .NET Aspire app host exposes several life cycles that you can hook into by i

To register a life cycle hook, implement the <xref:Aspire.Hosting.Lifecycle.IDistributedApplicationLifecycleHook> interface and register the hook with the app host using the <xref:Aspire.Hosting.Lifecycle.LifecycleHookServiceCollectionExtensions.AddLifecycleHook*> API:

:::code source="../fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/Program.cs":::
:::code source="../fundamentals/snippets/lifecycles/AspireApp/AspireApp.AppHost/AppHost.cs":::

The preceding code:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,21 @@
.WaitFor(apiService);

builder.Eventing.Subscribe<ResourceEndpointsAllocatedEvent>(
(@event, cancellationToken) =>
{
// The event doesn't expose an IServiceProvider, just write to the console.
Console.WriteLine($"""
3. '{@event.Resource.Name}' ResourceEndpointsAllocatedEvent
""");

return Task.CompletedTask;
});

builder.Eventing.Subscribe<BeforeStartEvent>(
static (@event, cancellationToken) =>
{
var logger = @event.Services.GetRequiredService<ILogger<Program>>();

logger.LogInformation("1. BeforeStartEvent");
logger.LogInformation("2. \"{ResourceName}\" ResourceEndpointsAllocatedEvent", @event.Resource.Name);

return Task.CompletedTask;
});

builder.Eventing.Subscribe<AfterEndpointsAllocatedEvent>(
builder.Eventing.Subscribe<BeforeStartEvent>(
static (@event, cancellationToken) =>
{
var logger = @event.Services.GetRequiredService<ILogger<Program>>();

logger.LogInformation("2. AfterEndpointsAllocatedEvent");
logger.LogInformation("1. BeforeStartEvent");

return Task.CompletedTask;
});
Expand All @@ -50,7 +39,7 @@
{
var logger = @event.Services.GetRequiredService<ILogger<Program>>();

logger.LogInformation("4. AfterResourcesCreatedEvent");
logger.LogInformation("3. AfterResourcesCreatedEvent");

return Task.CompletedTask;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.3.1" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.3.1" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.4.0-preview.1.25359.6" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.4.0-preview.1.25359.6" />
</ItemGroup>

</Project>
16 changes: 9 additions & 7 deletions docs/app-host/snippets/AspireApp/AspireApp.AppHost/Console.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
info: Program[0]
1. BeforeStartEvent
info: Aspire.Hosting.DistributedApplication[0]
Aspire version: 9.3.0-preview.1.25262.2+6d54dc081cd2e7ea435e33f7c0e62ff6946ae66d
Aspire version: 9.4.0
info: Aspire.Hosting.DistributedApplication[0]
Distributed application starting.
info: Aspire.Hosting.DistributedApplication[0]
Application host directory is: ../AspireApp/AspireApp.AppHost
info: Program[0]
2. AfterEndpointsAllocatedEvent
3. 'aspire-dashboard' ResourceEndpointsAllocatedEvent
3. 'cache' ResourceEndpointsAllocatedEvent
3. 'apiservice' ResourceEndpointsAllocatedEvent
3. 'webfrontend' ResourceEndpointsAllocatedEvent
2. "cache" ResourceEndpointsAllocatedEvent
info: Program[0]
2. "apiservice" ResourceEndpointsAllocatedEvent
info: Program[0]
2. "webfrontend" ResourceEndpointsAllocatedEvent
info: Program[0]
2. "aspire-dashboard" ResourceEndpointsAllocatedEvent
info: Aspire.Hosting.DistributedApplication[0]
Now listening on: https://localhost:17178
info: Aspire.Hosting.DistributedApplication[0]
Login to the dashboard at https://localhost:17178/login?t=<YOUR_TOKEN>
info: Program[0]
4. AfterResourcesCreatedEvent
3. AfterResourcesCreatedEvent
info: Aspire.Hosting.DistributedApplication[0]
Distributed application started. Press Ctrl+C to shut down.
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,52 @@

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

builder.Eventing.Subscribe<ResourceReadyEvent>(
cache.Resource,
static (@event, cancellationToken) =>
cache.OnResourceReady(static (resource, @event, cancellationToken) =>
{
var logger = @event.Services.GetRequiredService<ILogger<Program>>();

logger.LogInformation("4. ResourceReadyEvent");
logger.LogInformation("5. OnResourceReady");

return Task.CompletedTask;
});

builder.Eventing.Subscribe<InitializeResourceEvent>(cache.Resource,
static (@event, cancellationToken) =>
cache.OnInitializeResource(
static (resource, @event, cancellationToken) =>
{
var logger = @event.Services.GetRequiredService<ILogger<Program>>();

logger.LogInformation("1. InitializeResourceEvent");
logger.LogInformation("1. OnInitializeResource");

return Task.CompletedTask;
});

builder.Eventing.Subscribe<BeforeResourceStartedEvent>(
cache.Resource,
static (@event, cancellationToken) =>
cache.OnBeforeResourceStarted(
static (resource, @event, cancellationToken) =>
{
var logger = @event.Services.GetRequiredService<ILogger<Program>>();

logger.LogInformation("3. BeforeResourceStartedEvent");
logger.LogInformation("4. OnBeforeResourceStarted");


return Task.CompletedTask;
});

cache.OnResourceEndpointsAllocated(
static (resource, @event, cancellationToken) =>
{
var logger = @event.Services.GetRequiredService<ILogger<Program>>();

logger.LogInformation("2. OnResourceEndpointsAllocated");

return Task.CompletedTask;
});

builder.Eventing.Subscribe<ConnectionStringAvailableEvent>(
cache.Resource,
static (@event, cancellationToken) =>
cache.OnConnectionStringAvailable(
static (resource, @event, cancellationToken) =>
{
var logger = @event.Services.GetRequiredService<ILogger<Program>>();

logger.LogInformation("2. ConnectionStringAvailableEvent");
logger.LogInformation("3. OnConnectionStringAvailable");

return Task.CompletedTask;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.3.1" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.3.1" />
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.4.0-preview.1.25359.6" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.4.0-preview.1.25359.6" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
info: Aspire.Hosting.DistributedApplication[0]
Aspire version: 9.3.0
Aspire version: 9.4.0
info: Aspire.Hosting.DistributedApplication[0]
Distributed application starting.
info: Aspire.Hosting.DistributedApplication[0]
Application host directory is: ../AspireApp/AspireApp.AppHost
info: Program[0]
1. InitializeResourceEvent
1. OnInitializeResource
info: Program[0]
2. ConnectionStringAvailableEvent
2. OnResourceEndpointsAllocated
info: Program[0]
3. BeforeResourceStartedEvent
3. OnConnectionStringAvailable
info: Program[0]
4. OnBeforeResourceStarted
info: Aspire.Hosting.DistributedApplication[0]
Now listening on: https://localhost:17222
info: Aspire.Hosting.DistributedApplication[0]
Login to the dashboard at https://localhost:17222/login?t=<YOUR_TOKEN>
info: Program[0]
4. ResourceReadyEvent
5. OnResourceReady
info: Aspire.Hosting.DistributedApplication[0]
Distributed application started. Press Ctrl+C to shut down.
Loading
Loading