Extension methods on IDistributedApplicationBuilder for subscribing to application level events.#14119
Extension methods on IDistributedApplicationBuilder for subscribing to application level events.#14119afscrome wants to merge 4 commits intodotnet:mainfrom
IDistributedApplicationBuilder for subscribing to application level events.#14119Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14119Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14119" |
IDistributedApplicationBuilder for subscribing to application level events.IDistributedApplicationBuilder for subscribing to application level events.
There was a problem hiding this comment.
Pull request overview
This pull request introduces convenience extension methods on IDistributedApplicationBuilder for subscribing to application-level events, following up on PR #10097 which added similar methods for resource-level events. The PR adds four new extension methods (OnBeforeStart, OnAfterResourcesCreated, OnBeforePublish, OnAfterPublish) that wrap the underlying builder.Eventing.Subscribe<T> calls with a more fluent and discoverable API. Existing code across multiple files has been migrated to use these new helper methods.
Changes:
- Added four new public extension methods on
IDistributedApplicationBuilderfor subscribing to application events - Refactored existing
OnEventmethod toOnResourceEventfor clarity - Migrated 14+ call sites from
builder.Eventing.Subscribe<Event>to the newbuilder.OnEvent()syntax
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Aspire.Hosting/DistributedApplicationEventingExtensions.cs | Core implementation adding OnBeforeStart, OnAfterResourcesCreated, OnBeforePublish, OnAfterPublish methods and refactoring existing resource event methods |
| tests/Aspire.Hosting.Tests/OperationModesTests.cs | Updated test to use new OnAfterResourcesCreated method |
| src/Aspire.Hosting/ContainerRegistryResourceBuilderExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| src/Aspire.Hosting.Yarp/YarpResourceExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs | Migrated two BeforeStartEvent subscriptions to OnBeforeStart |
| src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs | Migrated three BeforeStartEvent subscriptions to OnBeforeStart |
| src/Aspire.Hosting.Maui/MauiOtlpExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs | CRITICAL: Contains corrupted code with malformed method bodies that will not compile |
| src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| src/Aspire.Hosting.Azure.Redis/AzureManagedRedisExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
| playground/DotnetTool/DotnetTool.AppHost/AppHost.cs | Migrated BeforeStartEvent subscription to OnBeforeStart |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// <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 <see cref="IDistributedApplicationBuilder"/>.</returns> | ||
| /// <remarks>If you need to ensure you only subscribe to the event once, see <see cref="Lifecycle.IDistributedApplicationEventingSubscriber"/>.</remarks> | ||
| 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="AfterResourcesCreatedEvent"/> 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 <see cref="IDistributedApplicationBuilder"/>.</returns> | ||
| /// <remarks>If you need to ensure you only subscribe to the event once, see <see cref="Lifecycle.IDistributedApplicationEventingSubscriber"/>.</remarks> | ||
| public static T OnAfterResourcesCreated<T>(this T builder, Func<AfterResourcesCreatedEvent, 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 <see cref="IDistributedApplicationBuilder"/>.</returns> | ||
| /// <remarks>If you need to ensure you only subscribe to the event once, see <see cref="Lifecycle.IDistributedApplicationEventingSubscriber"/>.</remarks> | ||
| 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 <see cref="IDistributedApplicationBuilder"/>.</returns> | ||
| /// <remarks>If you need to ensure you only subscribe to the event once, see <see cref="Lifecycle.IDistributedApplicationEventingSubscriber"/>.</remarks> | ||
| public static T OnAfterPublish<T>(this T builder, Func<AfterPublishEvent, CancellationToken, Task> callback) | ||
| where T : IDistributedApplicationBuilder | ||
| => builder.OnApplicationEvent(callback); |
There was a problem hiding this comment.
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.
…ubscribing to application level events. This is similar to dotnet#10097, but for app host level events. Moved over what consumers I (copilot really) could to the new helper - the main ones that couldn't be migrated were implementations of `IDistributedApplicationEventingSubscriber`, as they only get access to `IDistributedApplicationEventing`, and not the `IDistributedApplication` these extension method are added to. Since `IDistributedApplicationEventingSubscriber` is a bit of an advanced case, it doesn't seem unreasonable that they don't get the easy helper method. I left out `AfterEndpointsAllocatedEvent` from the new helper methods since it is obsolete.
8bdadd2 to
a050ca7
Compare
|
I think we want to export these methods using the new [AspireExport] attribute. Though I wonder if the generics will be a problem |
| /// <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> | ||
| [AspireExport("onAfterResourcesCreated", Description = "Subscribes a callback to the AfterResourcesCreatedEvent event within the AppHost.")] | ||
| public static T OnAfterResourcesCreated<T>(this T builder, Func<AfterResourcesCreatedEvent, CancellationToken, Task> callback) |
There was a problem hiding this comment.
Are you still planning on deprecating this event? In which case, does it make sense to not implement this helper / export it with AspireExport?
| /// <param name="callback">A callback to handle the event.</param> | ||
| /// <returns>The <see cref="IResourceBuilder{T}"/>.</returns> | ||
| /// <returns>The <paramref name="builder"/> for chaining.</returns> | ||
| [AspireExport("onInitializeResource", Description = "Subscribes a callback to the InitializeResourceEvent event of the resource.")] |
There was a problem hiding this comment.
Does it make sense to expose this event with AspireExport?. This event was introduced to allow custom resources to initialise themselves - does that level of initialisation make sense outside the app host itself?
|
Thanks for this contribution @afscrome! This is a nice API ergonomics improvement that makes event subscriptions more discoverable and consistent with the existing resource-level patterns. We'd like to take this change. Before we can merge, we just need a couple of things sorted out:
Also a minor fix needed: there's a missing newline at the end of Thanks again for the contribution! |
- Add tests for OnBeforeStart, OnAfterResourcesCreated, OnBeforePublish, OnAfterPublish - Add tests verifying builder is returned for method chaining - Fix missing newline at EOF in JavaScriptHostingExtensions.cs
|
I've added basic test coverage for the new application-level event helper methods in commit 6e475dc:
The docs-aspire issue is still needed before we can merge. |
Docs issues should be filed in |
#13149 hint hint @IEvangelist |
|
Docs issue: microsoft/aspire.dev#315 |
Yeah, fair callout. We're trying to automate that to remove the template altogether. |
|
@IEvangelist CI failing on the banner test |
Description
Moved over what consumers I (copilot really) could to the new helper - the main ones that couldn't be migrated were implementations of
IDistributedApplicationEventingSubscriber, as they only get access toIDistributedApplicationEventing, and not theIDistributedApplicationthese extension method are added to. SinceIDistributedApplicationEventingSubscriberis a bit of an advanced case, it doesn't seem unreasonable that they don't get the easy helper method.I left out
AfterEndpointsAllocatedEventfrom the new helper methods since it is obsolete.This is somewhat of a follow on to #10097, but for app host level events.
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
Checklist
<remarks />and<code />elements on your triple slash comments?