Skip to content
Merged
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
2 changes: 1 addition & 1 deletion playground/DotnetTool/DotnetTool.AppHost/AppHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@

// Some issues only show up when installing for first time, rather than using existing downloaded versions
// Use a specific NUGET_PACKAGES path for these playground tools, so we can easily reset them
builder.Eventing.Subscribe<BeforeStartEvent>(async (evt, _) =>
builder.OnBeforeStart(async (evt, _) =>
{
var nugetPackagesPath = Path.Join(evt.Services.GetRequiredService<IAspireStore>().BasePath, "nuget");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public static IResourceBuilder<AzureContainerRegistryResource> AddAzureContainer
/// </summary>
private static void SubscribeToAddRegistryTargetAnnotations(IDistributedApplicationBuilder builder, AzureContainerRegistryResource registry)
{
builder.Eventing.Subscribe<BeforeStartEvent>((beforeStartEvent, cancellationToken) =>
builder.OnBeforeStart((beforeStartEvent, cancellationToken) =>
{
foreach (var resource in beforeStartEvent.Model.Resources)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ public static IResourceBuilder<AzureCosmosDBResource> WithAccessKeyAuthenticatio
// need to do this later in case builder becomes an emulator after this method is called.
if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
{
builder.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((data, _) =>
builder.ApplicationBuilder.OnBeforeStart((data, _) =>
{
if (builder.Resource.IsEmulator)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ private static IResourceBuilder<AzureFunctionsProjectResource> AddAzureFunctions
.Resource;
}

builder.Eventing.Subscribe<BeforeStartEvent>((data, token) =>
builder.OnBeforeStart((data, token) =>
{
var removeStorage = true;
// Look at all of the resources and if none of them use the default storage, then we can remove it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ public static IResourceBuilder<AzurePostgresFlexibleServerResource> WithPassword
// need to do this later in case builder becomes an emulator after this method is called.
if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
{
builder.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((data, token) =>
builder.ApplicationBuilder.OnBeforeStart((data, token) =>
{
if (builder.Resource.IsContainer())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public static IResourceBuilder<AzureManagedRedisResource> WithAccessKeyAuthentic
// need to do this later in case builder becomes an emulator after this method is called.
if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
{
builder.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((data, token) =>
builder.ApplicationBuilder.OnBeforeStart((data, token) =>
{
if (builder.Resource.IsContainer())
{
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public static IResourceBuilder<AzureRedisCacheResource> WithAccessKeyAuthenticat
// need to do this later in case builder becomes an emulator after this method is called.
if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
{
builder.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((data, token) =>
builder.ApplicationBuilder.OnBeforeStart((data, token) =>
{
if (builder.Resource.IsContainer())
{
Expand Down
6 changes: 3 additions & 3 deletions src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public static IResourceBuilder<NodeAppResource> AddNodeApp(this IDistributedAppl

if (builder.ExecutionContext.IsRunMode)
{
builder.Eventing.Subscribe<BeforeStartEvent>((_, _) =>
builder.OnBeforeStart((_, _) =>
{
// set the command to the package manager executable if the JavaScriptRunScriptAnnotation is present
if (resourceBuilder.Resource.TryGetLastAnnotation<JavaScriptRunScriptAnnotation>(out _) &&
Expand Down Expand Up @@ -471,7 +471,7 @@ private static IResourceBuilder<TResource> CreateDefaultJavaScriptAppBuilder<TRe
// ensure the package manager command is set before starting the resource
if (builder.ExecutionContext.IsRunMode)
{
builder.Eventing.Subscribe<BeforeStartEvent>((_, _) =>
builder.OnBeforeStart((_, _) =>
{
if (resourceBuilder.Resource.TryGetLastAnnotation<JavaScriptPackageManagerAnnotation>(out var packageManager))
{
Expand Down Expand Up @@ -1139,7 +1139,7 @@ private static void AddInstaller<TResource>(IResourceBuilder<TResource> resource
.ExcludeFromManifest()
.WithCertificateTrustScope(CertificateTrustScope.None);

resource.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((_, _) =>
resource.ApplicationBuilder.OnBeforeStart((_, _) =>
{
// set the installer's working directory to match the resource's working directory
// and set the install command and args based on the resource's annotations
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting.Maui/MauiOtlpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ private static OtlpDevTunnelConfigurationAnnotation CreateOtlpDevTunnelInfrastru

// Manually allocate the stub endpoint so dev tunnel can start
// Dev tunnels wait for ResourceEndpointsAllocatedEvent before starting
appBuilder.Eventing.Subscribe<BeforeStartEvent>((evt, ct) =>
appBuilder.OnBeforeStart((evt, ct) =>
{
var endpoint = stubResource.Annotations.OfType<EndpointAnnotation>().FirstOrDefault();
if (endpoint is not null && endpoint.AllocatedEndpoint is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ private static IResourceBuilder<T> AddPythonAppCore<T>(
// and the dependencies will be established based on which resources actually exist
// Only do this in run mode since the installer and venv creator only run in run mode
var resourceToSetup = resourceBuilder.Resource;
builder.Eventing.Subscribe<BeforeStartEvent>((evt, ct) =>
builder.OnBeforeStart((evt, ct) =>
{
// Wire up wait dependencies for this resource based on which child resources exist
SetupDependencies(builder, resourceToSetup);
Expand Down Expand Up @@ -1332,7 +1332,7 @@ private static void AddInstaller<T>(IResourceBuilder<T> builder, bool install) w
installerBuilder.WithExplicitStart();
}

builder.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((_, _) =>
builder.ApplicationBuilder.OnBeforeStart((_, _) =>
{
// Set the installer's working directory to match the resource's working directory
// and set the install command and args based on the resource's annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public static IResourceBuilder<ContainerRegistryResource> AddContainerRegistry(
/// </summary>
private static void SubscribeToAddRegistryTargetAnnotations(IDistributedApplicationBuilder builder, ContainerRegistryResource registry)
{
builder.Eventing.Subscribe<BeforeStartEvent>((beforeStartEvent, cancellationToken) =>
builder.OnBeforeStart((beforeStartEvent, cancellationToken) =>
{
foreach (var resource in beforeStartEvent.Model.Resources)
{
Expand Down
71 changes: 58 additions & 13 deletions src/Aspire.Hosting/DistributedApplicationEventingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,51 @@

using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Eventing;
using Aspire.Hosting.Publishing;

namespace Aspire.Hosting;

/// <summary>
/// Provides extension methods for subscribing to <see cref="IDistributedApplicationResourceEvent"/> events on resources.
/// Provides extension methods for subscribing to <see cref="IDistributedApplicationEvent"/> and <see cref="IDistributedApplicationResourceEvent"/> events.
/// </summary>
public static class DistributedApplicationEventingExtensions
{
/// <summary>
/// Subscribes a callback to the <see cref="BeforeStartEvent"/> event within the AppHost.
/// </summary>
/// <param name="builder">The distributed application builder.</param>
/// <param name="callback">A callback to handle the event.</param>
/// <returns>The <paramref name="builder"/> for chaining.</returns>
/// <remarks>If you need to ensure you only subscribe to the event once, see <see cref="Lifecycle.IDistributedApplicationEventingSubscriber"/>.</remarks>
[AspireExportIgnore(Reason = "Complex generic delegates with event/CancellationToken types — not ATS-compatible.")]
public static T OnBeforeStart<T>(this T builder, Func<BeforeStartEvent, CancellationToken, Task> callback)
where T : IDistributedApplicationBuilder
=> builder.OnApplicationEvent(callback);

/// <summary>
/// Subscribes a callback to the <see cref="BeforePublishEvent"/> event within the AppHost.
/// </summary>
/// <param name="builder">The distributed application builder.</param>
/// <param name="callback">A callback to handle the event.</param>
/// <returns>The <paramref name="builder"/> for chaining.</returns>
/// <remarks>If you need to ensure you only subscribe to the event once, see <see cref="Lifecycle.IDistributedApplicationEventingSubscriber"/>.</remarks>
[AspireExportIgnore(Reason = "Complex generic delegates with event/CancellationToken types — not ATS-compatible.")]
public static T OnBeforePublish<T>(this T builder, Func<BeforePublishEvent, CancellationToken, Task> callback)
where T : IDistributedApplicationBuilder
=> builder.OnApplicationEvent(callback);

/// <summary>
/// Subscribes a callback to the <see cref="AfterPublishEvent"/> event within the AppHost.
/// </summary>
/// <param name="builder">The distributed application builder.</param>
/// <param name="callback">A callback to handle the event.</param>
/// <returns>The <paramref name="builder"/> for chaining.</returns>
/// <remarks>If you need to ensure you only subscribe to the event once, see <see cref="Lifecycle.IDistributedApplicationEventingSubscriber"/>.</remarks>
[AspireExportIgnore(Reason = "Complex generic delegates with event/CancellationToken types — not ATS-compatible.")]
public static T OnAfterPublish<T>(this T builder, Func<AfterPublishEvent, CancellationToken, Task> callback)
where T : IDistributedApplicationBuilder
=> builder.OnApplicationEvent(callback);
Comment on lines +15 to +49
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding code examples to these new public API methods to demonstrate typical usage patterns. While the related resource event methods from PR #10097 also lack examples, providing them would improve developer experience. For instance, showing how to use OnBeforeStart to access services and modify the application model before startup would be valuable. The XML documentation coding guidelines recommend examples for public APIs, especially for new extension methods.

Copilot generated this review using guidance from repository custom instructions.

/// <summary>
/// Subscribes a callback to the <see cref="BeforeResourceStartedEvent"/> event within the AppHost.
/// </summary>
Expand All @@ -22,10 +59,10 @@ public static class DistributedApplicationEventingExtensions
[AspireExportIgnore(Reason = "Complex generic delegates with event/CancellationToken types — not ATS-compatible.")]
public static IResourceBuilder<T> OnBeforeResourceStarted<T>(this IResourceBuilder<T> builder, Func<T, BeforeResourceStartedEvent, CancellationToken, Task> callback)
where T : IResource
=> builder.OnEvent(callback);
=> builder.OnResourceEvent(callback);

/// <summary>
/// Subscribes a callback to the <see cref="ResourceStoppedEvent"/> event within the AppHost.
/// Subscribes a callback to the <see cref="ResourceStoppedEvent"/> event for <paramref name="builder"/>.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
Expand All @@ -35,10 +72,10 @@ public static IResourceBuilder<T> OnBeforeResourceStarted<T>(this IResourceBuild
[AspireExportIgnore(Reason = "Complex generic delegates with event/CancellationToken types — not ATS-compatible.")]
public static IResourceBuilder<T> OnResourceStopped<T>(this IResourceBuilder<T> builder, Func<T, ResourceStoppedEvent, CancellationToken, Task> callback)
where T : IResource
=> builder.OnEvent(callback);
=> builder.OnResourceEvent(callback);

/// <summary>
/// Subscribes a callback to the <see cref="ConnectionStringAvailableEvent"/> event within the AppHost.
/// Subscribes a callback to the <see cref="ConnectionStringAvailableEvent"/> event for <paramref name="builder"/>.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
Expand All @@ -48,10 +85,10 @@ public static IResourceBuilder<T> OnResourceStopped<T>(this IResourceBuilder<T>
[AspireExportIgnore(Reason = "Complex generic delegates with event/CancellationToken types — not ATS-compatible.")]
public static IResourceBuilder<T> OnConnectionStringAvailable<T>(this IResourceBuilder<T> builder, Func<T, ConnectionStringAvailableEvent, CancellationToken, Task> callback)
where T : IResourceWithConnectionString
=> builder.OnEvent(callback);
=> builder.OnResourceEvent(callback);

/// <summary>
/// Subscribes a callback to the <see cref="InitializeResourceEvent"/> event within the AppHost.
/// Subscribes a callback to the <see cref="InitializeResourceEvent"/> event for <paramref name="builder"/>.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
Expand All @@ -61,10 +98,10 @@ public static IResourceBuilder<T> OnConnectionStringAvailable<T>(this IResourceB
[AspireExportIgnore(Reason = "Complex generic delegates with event/CancellationToken types — not ATS-compatible.")]
public static IResourceBuilder<T> OnInitializeResource<T>(this IResourceBuilder<T> builder, Func<T, InitializeResourceEvent, CancellationToken, Task> callback)
where T : IResource
=> builder.OnEvent(callback);
=> builder.OnResourceEvent(callback);

/// <summary>
/// Subscribes a callback to the <see cref="ResourceEndpointsAllocatedEvent"/> event within the AppHost.
/// Subscribes a callback to the <see cref="ResourceEndpointsAllocatedEvent"/> event for <paramref name="builder"/>.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
Expand All @@ -74,10 +111,10 @@ public static IResourceBuilder<T> OnInitializeResource<T>(this IResourceBuilder<
[AspireExportIgnore(Reason = "Complex generic delegates with event/CancellationToken types — not ATS-compatible.")]
public static IResourceBuilder<T> OnResourceEndpointsAllocated<T>(this IResourceBuilder<T> builder, Func<T, ResourceEndpointsAllocatedEvent, CancellationToken, Task> callback)
where T : IResourceWithEndpoints
=> builder.OnEvent(callback);
=> builder.OnResourceEvent(callback);

/// <summary>
/// Subscribes a callback to the <see cref="ResourceReadyEvent"/> event within the AppHost.
/// Subscribes a callback to the <see cref="ResourceReadyEvent"/> event for <paramref name="builder"/>.
/// </summary>
/// <typeparam name="T">The resource type.</typeparam>
/// <param name="builder">The resource builder.</param>
Expand All @@ -87,9 +124,17 @@ public static IResourceBuilder<T> OnResourceEndpointsAllocated<T>(this IResource
[AspireExportIgnore(Reason = "Complex generic delegates with event/CancellationToken types — not ATS-compatible.")]
public static IResourceBuilder<T> OnResourceReady<T>(this IResourceBuilder<T> builder, Func<T, ResourceReadyEvent, CancellationToken, Task> callback)
where T : IResource
=> builder.OnEvent(callback);
=> builder.OnResourceEvent(callback);

private static T OnApplicationEvent<T, TEvent>(this T builder, Func<TEvent, CancellationToken, Task> callback)
where T : IDistributedApplicationBuilder
where TEvent : IDistributedApplicationEvent
{
builder.Eventing.Subscribe(callback);
return builder;
}

private static IResourceBuilder<TResource> OnEvent<TResource, TEvent>(this IResourceBuilder<TResource> builder, Func<TResource, TEvent, CancellationToken, Task> callback)
private static IResourceBuilder<TResource> OnResourceEvent<TResource, TEvent>(this IResourceBuilder<TResource> builder, Func<TResource, TEvent, CancellationToken, Task> callback)
where TResource : IResource
where TEvent : IDistributedApplicationResourceEvent
{
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting/ResourceBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2892,7 +2892,7 @@ public static IResourceBuilder<TResource> SubscribeHttpsEndpointsUpdate<TResourc
ArgumentNullException.ThrowIfNull(callback);

var resource = builder.Resource;
builder.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((@event, cancellationToken) =>
builder.ApplicationBuilder.OnBeforeStart((@event, cancellationToken) =>
{
var developerCertificateService = @event.Services.GetRequiredService<IDeveloperCertificateService>();

Expand Down
Loading
Loading