Skip to content

Commit

Permalink
.NET Aspire 9.0 - GA Release content (#1867)
Browse files Browse the repository at this point in the history
* Initial bits...WIP, early draft

* Add draft SDK content

* A few more bits

* SDK

* Tons of updates, additions, removals, and whatnot.

* Sweeping updates

* Fix links

* Initial parts of eventing

* Fix a few build issues

* More issues

* Add eventing to TOC

* Added txt for console output

* Eventing API content, getting closer

* A bit more clean up

* Fix hosting meilisearch

* More SDK updates

* More SDK clarification

* index tweaks

* Try using uppercase slug

* Fix publish API

* Add zones to upgrade

* Some breaking changes

* More links to API change

* Add API removal text

* TOC updates

* More breaking changes and fix podman

* Trying a few things with the TOC

* Generate the breaking-changes

* Clean up

* Add AI metadata

* Fix TOC

* Add a few more bits for the upgrade content

* Fix lightbox attribute

* Missing attribute

* New line

* Edit pass

* Upgrade quickstart to .NET Aspire 9.0, and update some dashboard images

* Update dashboard bits, images, and add actions section

* Fix link, no troubleshooting

* More resource action updates

* Remove edges

* Fixes #1823

* Add details for app host lifecylce

* Demote alert

* Edit pass

* Edit pass and additions.

* Edit pass and highlighting

* Let's see how far we've come

* Lots of updates

* Move a few things

* Added error

* Remove discards.

* Apply suggestions from code review

Co-authored-by: Safia Abdalla <safia@safia.rocks>

* Additional feedback

* Fix learn site issue reporting.

* Fix link...

* Fixes #2003

* Edit pass on explore dashboard

* More clean up

* Add details for WaitFor/Completion APIs.

* Fixes from build report.

* Remove snippets for Functions

* Update overview.md

* Update dotnet-aspire-sdk.md

* Update eventing.md

* Update eventing.md

* Update docs/app-host/eventing.md

* Add Ollama integration updates.

* Fix headings and sections

* Lots of updates and fixes

* Upgrade a few more bits, quickstart and setup/tooling

* Fix build

* Additions, version fixes, etc.

* Clean up some of the image/diagrams

* Fixing updated APIs

* Added a few bits to address #1704

* Addresses #2004

* Apply suggestions from code review

Co-authored-by: Aaron Powell <me@aaron-powell.com>

* Fix broken link

* Rewrite the toolkit overview. Update some images

* Remove images

* Fix link and minor edits

* Move Azure Cache for Redis to the bottom

* Fix MD lint

* Edit pass on the Orleans content. Also, fixes #1818

* Move Azure PostgreSQL bits to the bottom of the page

* Try fixing the zones

* Fix MD lint error

* Fix more broken zones

* Add see also section

* Setup and tooling TOC updates

* Re-order TOC

* Update docs/app-host/eventing.md

* Updates for the tutorial on adding Aspire to existing app

* Repurpose RC1, redirects, edit pass, links, etc.

* Fix the TOC

* Edit pass and cleanup of explore and what's new in .NET Aspire 9

* Added custom command article

* Add to TOC

* Fix #2016, update to latest templates

* Fix #2022

* A bit of container love

* A bit more clean up

* Be more consistent with references to NuGet packages

* Fix #2027

* Fix regex replace. :|

* Fix #2026

* Apply suggestions from code review

Co-authored-by: Aaron Powell <me@aaron-powell.com>

* A few bits of peer feedback

* Some fixes related to #2024

* Fix #2017

* Apply suggestions from code review

Co-authored-by: Jose Perez Rodriguez <joperezr@microsoft.com>

* Fix suggestion

* Update docs/fundamentals/aspire-sdk-templates.md

Co-authored-by: Jose Perez Rodriguez <joperezr@microsoft.com>

* A few peer bits of feedback

* Fix (update) create new project images

* Fix include and delete legacy media

* Isolate to selection, readd media

* Fix #1993

* A few more bits of cleanup

* Remove .NET Aspire SDK as a prereq

* A few bits of additional context.

* More links

* Tidy

* Consistent sentence

* Bump DOTNET_VERSION

* Remove install of workload

---------

Co-authored-by: Safia Abdalla <safia@safia.rocks>
Co-authored-by: Mitch Denny <midenn@microsoft.com>
Co-authored-by: Aaron Powell <me@aaron-powell.com>
Co-authored-by: Jose Perez Rodriguez <joperezr@microsoft.com>
  • Loading branch information
5 people authored Nov 12, 2024
1 parent 8fabcfa commit 898963f
Show file tree
Hide file tree
Showing 446 changed files with 68,321 additions and 2,425 deletions.
7 changes: 1 addition & 6 deletions .github/workflows/snippets5000.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
default: "Manual run"

env:
DOTNET_VERSION: '8.0.100' # set this to the dot net version to use
DOTNET_VERSION: '9.0.x' # set this to the dot net version to use
EnableNuGetPackageRestore: "True"

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
Expand Down Expand Up @@ -46,11 +46,6 @@ jobs:
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

# Install workload
- name: Install .NET Aspire workload
run: |
dotnet workload install aspire
# Print dotnet info
- name: Display .NET info
run: |
Expand Down
4 changes: 4 additions & 0 deletions .openpublishing.redirection.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@
{
"source_path_from_root": "/docs/fundamentals/testing.md",
"redirect_url": "/dotnet/aspire/testing/write-your-first-test"
},
{
"source_path_from_root": "/docs/whats-new/dotnet-aspire-9-release-candidate-1.md",
"redirect_url": "/dotnet/aspire/whats-new/dotnet-aspire-9"
}
]
}
4 changes: 3 additions & 1 deletion docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
"open_source_feedback_issueTitle": "",
"open_source_feedback_productLogoLightUrl": "https://learn.microsoft.com/media/logos/logo_net.svg",
"open_source_feedback_productLogoDarkUrl": "https://learn.microsoft.com/media/logos/logo_net.svg",
"open_source_feedback_issueUrl": "https://github.com/dotnet/docs-aspire/issues/new?template=customer-feedback.yml",
"open_source_feedback_issueUrl": "https://github.com/dotnet/docs-aspire/issues/new?template=z-customer-feedback.yml",
"open_source_feedback_productName": ".NET Aspire",
"open_source_feedback_productDescription": "The .NET Aspire documentation is open source and we love community feedback. Please provide feedback by posting an issue on GitHub, or product feedback here:",
"social_image_url": "/dotnet/media/dotnet-aspire-logo.png",
Expand Down Expand Up @@ -154,6 +154,7 @@
"cookie",
"Cosmos DB",
"Dapr",
"Deno",
"Docker",
"Dockerfile",
"EF Core",
Expand Down Expand Up @@ -181,6 +182,7 @@
"Postgres",
"PostgreSQL",
"Privacy",
"Python",
"RabbitMQ",
"React",
"Redis",
Expand Down
87 changes: 87 additions & 0 deletions docs/app-host/eventing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
title: Eventing in .NET Aspire
description: Learn how to use the .NET eventing features with .NET Aspire.
ms.date: 10/30/2024
---

# Eventing in .NET Aspire

In .NET Aspire, eventing allows you to publish and subscribe to events during various [app host life cycles](xref:dotnet/aspire/app-host#app-host-life-cycles). Eventing is more flexible than life cycle events. Both let you run arbitrary code during event callbacks, but eventing offers finer control of event timing, publishing, and provides supports for custom events.

The eventing mechanisms in .NET Aspire are part of the [📦 Aspire.Hosting](https://www.nuget.org/packages/Aspire.Hosting) NuGet package. This package provides a set of interfaces and classes in the <xref:Aspire.Hosting.Eventing> namespace that you use to publish and subscribe to events in your .NET Aspire app host project. Eventing is scoped to the app host itself and the resources within.

In this article, you learn how to use the eventing features in .NET Aspire.

## App host eventing

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.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:

:::code source="snippets/AspireApp/AspireApp.AppHost/Program.cs" highlight="17-25,27-35,37-45":::

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,16":::

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`, 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. `ConnectionStringAvailableEvent`: Raised when a connection string becomes available for a resource.
1. `BeforeResourceStartedEvent`: Raised before the orchestrator starts a new resource.
1. `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:

:::code source="snippets/AspireApp/AspireApp.ResourceAppHost/Program.cs" highlight="8-17,19-28,30-39":::

The preceding code subscribes to the `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.

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":::

> [!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).
## Publish events

When subscribing to any of the built-in events, you don't need to publish the event yourself as the app host orchestrator manages to publish built-in events on your behalf. However, you can publish custom events with the eventing API. To publish an event, you have to first define an event as an implementation of either the <xref:Aspire.Hosting.Eventing.IDistributedApplicationEvent> or <xref:Aspire.Hosting.Eventing.IDistributedApplicationResourceEvent> interface. You need to determine which interface to implement based on whether the event is a global app host event or a resource-specific event.

Then, you can subscribe and publish the event by calling the either of the following APIs:

- <xref:Aspire.Hosting.Eventing.IDistributedApplicationEventing.PublishAsync``1(``0,System.Threading.CancellationToken)>: Publishes an event to all subscribes of the specific event type.
- `PublishAsync<T>(T, EventDispatchBehavior, CancellationToken)`: Publishes an event to all subscribes of the specific event type with a specified dispatch behavior.

### Provide an `EventDispatchBehavior`

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>: Fires events sequentially and blocks until they're all processed.
- <xref:Aspire.Hosting.Eventing.EventDispatchBehavior.BlockingConcurrent>: Fires events concurrently and blocks until they are all processed.
- <xref:Aspire.Hosting.Eventing.EventDispatchBehavior.NonBlockingSequential>: Fires events sequentially but doesn't block.
- <xref:Aspire.Hosting.Eventing.EventDispatchBehavior.NonBlockingConcurrent>: Fires events concurrently but doesn't block.
-->

- `EventDispatchBehavior.BlockingSequential`: Fires events sequentially and blocks until they're all processed.
- `EventDispatchBehavior.BlockingConcurrent`: Fires events concurrently and blocks until they're all processed.
- `EventDispatchBehavior.NonBlockingSequential`: Fires events sequentially but doesn't block.
- `EventDispatchBehavior.NonBlockingConcurrent`: 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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\AspireApp.ServiceDefaults\AspireApp.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
39 changes: 39 additions & 0 deletions docs/app-host/snippets/AspireApp/AspireApp.ApiService/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
var builder = WebApplication.CreateBuilder(args);

// Add service defaults & Aspire client integrations.
builder.AddServiceDefaults();

// Add services to the container.
builder.Services.AddProblemDetails();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.UseExceptionHandler();

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;
});

app.MapDefaultEndpoints();

app.Run();

record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "http://localhost:5562",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "https://localhost:7482;http://localhost:5562",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>7b352f08-305b-4032-9a21-90deb02efc04</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\AspireApp.ApiService\AspireApp.ApiService.csproj" />
<ProjectReference Include="..\AspireApp.Web\AspireApp.Web.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.0.0" />
</ItemGroup>

</Project>
18 changes: 18 additions & 0 deletions docs/app-host/snippets/AspireApp/AspireApp.AppHost/Console.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
info: Program[0]
1. BeforeStartEvent
info: Aspire.Hosting.DistributedApplication[0]
Aspire version: 9.0.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
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]
3. AfterResourcesCreatedEvent
info: Aspire.Hosting.DistributedApplication[0]
Distributed application started. Press Ctrl+C to shut down.
47 changes: 47 additions & 0 deletions docs/app-host/snippets/AspireApp/AspireApp.AppHost/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

var builder = DistributedApplication.CreateBuilder(args);

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

var apiService = builder.AddProject<Projects.AspireApp_ApiService>("apiservice");

builder.AddProject<Projects.AspireApp_Web>("webfrontend")
.WithExternalHttpEndpoints()
.WithReference(cache)
.WaitFor(cache)
.WithReference(apiService)
.WaitFor(apiService);

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

logger.LogInformation("1. BeforeStartEvent");

return Task.CompletedTask;
});

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

logger.LogInformation("2. AfterEndpointsAllocatedEvent");

return Task.CompletedTask;
});

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

logger.LogInformation("3. AfterResourcesCreatedEvent");

return Task.CompletedTask;
});

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17178;http://localhost:15142",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21137",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22259"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15142",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19091",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20229"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
Loading

0 comments on commit 898963f

Please sign in to comment.