diff --git a/playground/DotnetTool/DotnetTool.AppHost/AppHost.cs b/playground/DotnetTool/DotnetTool.AppHost/AppHost.cs index c16701620c9..9c42444d2ce 100644 --- a/playground/DotnetTool/DotnetTool.AppHost/AppHost.cs +++ b/playground/DotnetTool/DotnetTool.AppHost/AppHost.cs @@ -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(async (evt, _) => +builder.OnBeforeStart(async (evt, _) => { var nugetPackagesPath = Path.Join(evt.Services.GetRequiredService().BasePath, "nuget"); diff --git a/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs b/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs index 2be87f443be..595b51cdbe1 100644 --- a/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs +++ b/src/Aspire.Hosting.Azure.ContainerRegistry/AzureContainerRegistryExtensions.cs @@ -76,7 +76,7 @@ public static IResourceBuilder AddAzureContainer /// private static void SubscribeToAddRegistryTargetAnnotations(IDistributedApplicationBuilder builder, AzureContainerRegistryResource registry) { - builder.Eventing.Subscribe((beforeStartEvent, cancellationToken) => + builder.OnBeforeStart((beforeStartEvent, cancellationToken) => { foreach (var resource in beforeStartEvent.Model.Resources) { diff --git a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs index 42d1adfaa80..61d80e70ce2 100644 --- a/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs +++ b/src/Aspire.Hosting.Azure.CosmosDB/AzureCosmosDBExtensions.cs @@ -408,7 +408,7 @@ public static IResourceBuilder 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((data, _) => + builder.ApplicationBuilder.OnBeforeStart((data, _) => { if (builder.Resource.IsEmulator) { diff --git a/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs b/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs index 2dcf8c1e75c..19929a3a75c 100644 --- a/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs +++ b/src/Aspire.Hosting.Azure.Functions/AzureFunctionsProjectResourceExtensions.cs @@ -148,7 +148,7 @@ private static IResourceBuilder AddAzureFunctions .Resource; } - builder.Eventing.Subscribe((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. diff --git a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs index 16b581cc253..e1e2726ff2d 100644 --- a/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs +++ b/src/Aspire.Hosting.Azure.PostgreSQL/AzurePostgresExtensions.cs @@ -300,7 +300,7 @@ public static IResourceBuilder 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((data, token) => + builder.ApplicationBuilder.OnBeforeStart((data, token) => { if (builder.Resource.IsContainer()) { diff --git a/src/Aspire.Hosting.Azure.Redis/AzureManagedRedisExtensions.cs b/src/Aspire.Hosting.Azure.Redis/AzureManagedRedisExtensions.cs index c243fd4d5f6..f63f8fdc9b3 100644 --- a/src/Aspire.Hosting.Azure.Redis/AzureManagedRedisExtensions.cs +++ b/src/Aspire.Hosting.Azure.Redis/AzureManagedRedisExtensions.cs @@ -133,7 +133,7 @@ public static IResourceBuilder 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((data, token) => + builder.ApplicationBuilder.OnBeforeStart((data, token) => { if (builder.Resource.IsContainer()) { diff --git a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs index 67ffdd24aa0..2f8492d3dcb 100644 --- a/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs +++ b/src/Aspire.Hosting.Azure.Redis/AzureRedisExtensions.cs @@ -206,7 +206,7 @@ public static IResourceBuilder 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((data, token) => + builder.ApplicationBuilder.OnBeforeStart((data, token) => { if (builder.Resource.IsContainer()) { diff --git a/src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs b/src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs index 4d1bd7d52a6..3e56cafc281 100644 --- a/src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs +++ b/src/Aspire.Hosting.JavaScript/JavaScriptHostingExtensions.cs @@ -260,7 +260,7 @@ public static IResourceBuilder AddNodeApp(this IDistributedAppl if (builder.ExecutionContext.IsRunMode) { - builder.Eventing.Subscribe((_, _) => + builder.OnBeforeStart((_, _) => { // set the command to the package manager executable if the JavaScriptRunScriptAnnotation is present if (resourceBuilder.Resource.TryGetLastAnnotation(out _) && @@ -463,7 +463,7 @@ private static IResourceBuilder CreateDefaultJavaScriptAppBuilder((_, _) => + builder.OnBeforeStart((_, _) => { if (resourceBuilder.Resource.TryGetLastAnnotation(out var packageManager)) { @@ -631,7 +631,7 @@ public static IResourceBuilder AddViteApp(this IDistributedAppl if (builder.ExecutionContext.IsRunMode) { - builder.Eventing.Subscribe((@event, _) => + builder.OnBeforeStart((@event, _) => { var developerCertificateService = @event.Services.GetRequiredService(); @@ -985,7 +985,7 @@ private static void AddInstaller(IResourceBuilder resource .ExcludeFromManifest() .WithCertificateTrustScope(CertificateTrustScope.None); - resource.ApplicationBuilder.Eventing.Subscribe((_, _) => + 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 diff --git a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs index 94bbb74bd30..71449b78847 100644 --- a/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.Keycloak/KeycloakResourceBuilderExtensions.cs @@ -106,7 +106,7 @@ public static IResourceBuilder AddKeycloak( if (builder.ExecutionContext.IsRunMode) { - builder.Eventing.Subscribe((@event, cancellationToken) => + builder.OnBeforeStart((@event, cancellationToken) => { var developerCertificateService = @event.Services.GetRequiredService(); diff --git a/src/Aspire.Hosting.Maui/MauiOtlpExtensions.cs b/src/Aspire.Hosting.Maui/MauiOtlpExtensions.cs index a7af3faf05e..b0835139765 100644 --- a/src/Aspire.Hosting.Maui/MauiOtlpExtensions.cs +++ b/src/Aspire.Hosting.Maui/MauiOtlpExtensions.cs @@ -115,7 +115,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((evt, ct) => + appBuilder.OnBeforeStart((evt, ct) => { var endpoint = stubResource.Annotations.OfType().FirstOrDefault(); if (endpoint is not null && endpoint.AllocatedEndpoint is null) diff --git a/src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs b/src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs index 005dd123309..941de5073e7 100644 --- a/src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs @@ -312,7 +312,7 @@ public static IResourceBuilder AddUvicornApp( if (builder.ExecutionContext.IsRunMode) { - builder.Eventing.Subscribe((@event, cancellationToken) => + builder.OnBeforeStart((@event, cancellationToken) => { var developerCertificateService = @event.Services.GetRequiredService(); @@ -510,7 +510,7 @@ private static IResourceBuilder AddPythonAppCore( // 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((evt, ct) => + builder.OnBeforeStart((evt, ct) => { // Wire up wait dependencies for this resource based on which child resources exist SetupDependencies(builder, resourceToSetup); @@ -1346,7 +1346,7 @@ private static void AddInstaller(IResourceBuilder builder, bool install) w installerBuilder.WithExplicitStart(); } - builder.ApplicationBuilder.Eventing.Subscribe((_, _) => + 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 diff --git a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs index dbbe0716d8c..cfd79d8671d 100644 --- a/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs +++ b/src/Aspire.Hosting.Redis/RedisBuilderExtensions.cs @@ -175,7 +175,7 @@ public static IResourceBuilder AddRedis( if (builder.ExecutionContext.IsRunMode) { - builder.Eventing.Subscribe((@event, cancellationToken) => + builder.OnBeforeStart((@event, cancellationToken) => { var developerCertificateService = @event.Services.GetRequiredService(); @@ -387,7 +387,7 @@ public static IResourceBuilder WithRedisInsight(this IResourceBui }) .ExcludeFromManifest(); - builder.ApplicationBuilder.Eventing.Subscribe((@event, cancellationToken) => + builder.ApplicationBuilder.OnBeforeStart((@event, cancellationToken) => { var developerCertificateService = @event.Services.GetRequiredService(); diff --git a/src/Aspire.Hosting.Yarp/YarpResourceExtensions.cs b/src/Aspire.Hosting.Yarp/YarpResourceExtensions.cs index a4416fe29a3..b5a674b5025 100644 --- a/src/Aspire.Hosting.Yarp/YarpResourceExtensions.cs +++ b/src/Aspire.Hosting.Yarp/YarpResourceExtensions.cs @@ -55,7 +55,7 @@ public static IResourceBuilder AddYarp( if (builder.ExecutionContext.IsRunMode) { - builder.Eventing.Subscribe((@event, cancellationToken) => + builder.OnBeforeStart((@event, cancellationToken) => { var developerCertificateService = @event.Services.GetRequiredService(); diff --git a/src/Aspire.Hosting/ContainerRegistryResourceBuilderExtensions.cs b/src/Aspire.Hosting/ContainerRegistryResourceBuilderExtensions.cs index 70f95b21bff..8693a992f81 100644 --- a/src/Aspire.Hosting/ContainerRegistryResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ContainerRegistryResourceBuilderExtensions.cs @@ -118,7 +118,7 @@ public static IResourceBuilder AddContainerRegistry( /// private static void SubscribeToAddRegistryTargetAnnotations(IDistributedApplicationBuilder builder, ContainerRegistryResource registry) { - builder.Eventing.Subscribe((beforeStartEvent, cancellationToken) => + builder.OnBeforeStart((beforeStartEvent, cancellationToken) => { foreach (var resource in beforeStartEvent.Model.Resources) { diff --git a/src/Aspire.Hosting/DistributedApplicationEventingExtensions.cs b/src/Aspire.Hosting/DistributedApplicationEventingExtensions.cs index ab29b72142c..d0b12a9e0bf 100644 --- a/src/Aspire.Hosting/DistributedApplicationEventingExtensions.cs +++ b/src/Aspire.Hosting/DistributedApplicationEventingExtensions.cs @@ -3,81 +3,144 @@ using Aspire.Hosting.ApplicationModel; using Aspire.Hosting.Eventing; +using Aspire.Hosting.Publishing; namespace Aspire.Hosting; /// -/// Provides extension methods for subscribing to events on resources. +/// Provides extension methods for subscribing to and events. /// public static class DistributedApplicationEventingExtensions { + /// + /// Subscribes a callback to the event within the AppHost. + /// + /// The distributed application builder. + /// A callback to handle the event. + /// The for chaining. + /// If you need to ensure you only subscribe to the event once, see . + [AspireExport("onBeforeStart", Description = "Subscribes a callback to the BeforeStartEvent event within the AppHost.")] + public static T OnBeforeStart(this T builder, Func callback) + where T : IDistributedApplicationBuilder + => builder.OnApplicationEvent(callback); + + /// + /// Subscribes a callback to the event within the AppHost. + /// + /// The distributed application builder. + /// A callback to handle the event. + /// The for chaining. + /// If you need to ensure you only subscribe to the event once, see . + [AspireExport("onAfterResourcesCreated", Description = "Subscribes a callback to the AfterResourcesCreatedEvent event within the AppHost.")] + public static T OnAfterResourcesCreated(this T builder, Func callback) + where T : IDistributedApplicationBuilder + => builder.OnApplicationEvent(callback); + + /// + /// Subscribes a callback to the event within the AppHost. + /// + /// The distributed application builder. + /// A callback to handle the event. + /// The for chaining. + /// If you need to ensure you only subscribe to the event once, see . + [AspireExport("onBeforePublish", Description = "Subscribes a callback to the BeforePublishEvent event within the AppHost.")] + public static T OnBeforePublish(this T builder, Func callback) + where T : IDistributedApplicationBuilder + => builder.OnApplicationEvent(callback); + + /// + /// Subscribes a callback to the event within the AppHost. + /// + /// The distributed application builder. + /// A callback to handle the event. + /// The for chaining. + /// If you need to ensure you only subscribe to the event once, see . + [AspireExport("onAfterPublish", Description = "Subscribes a callback to the AfterPublishEvent event within the AppHost.")] + public static T OnAfterPublish(this T builder, Func callback) + where T : IDistributedApplicationBuilder + => builder.OnApplicationEvent(callback); + /// /// Subscribes a callback to the event within the AppHost. /// /// The resource type. /// The resource builder. /// A callback to handle the event. - /// The . + /// The for chaining. + [AspireExport("onBeforeResourceStarted", Description = "Subscribes a callback to the BeforeResourceStartedEvent event of the resource.")] public static IResourceBuilder OnBeforeResourceStarted(this IResourceBuilder builder, Func callback) where T : IResource - => builder.OnEvent(callback); + => builder.OnResourceEvent(callback); /// - /// Subscribes a callback to the event within the AppHost. + /// Subscribes a callback to the event for . /// /// The resource type. /// The resource builder. /// A callback to handle the event. - /// The . + /// The for chaining. + [AspireExport("onResourceStopped", Description = "Subscribes a callback to the ResourceStoppedEvent event of the resource.")] public static IResourceBuilder OnResourceStopped(this IResourceBuilder builder, Func callback) where T : IResource - => builder.OnEvent(callback); + => builder.OnResourceEvent(callback); /// - /// Subscribes a callback to the event within the AppHost. + /// Subscribes a callback to the event for . /// /// The resource type. /// The resource builder. /// A callback to handle the event. - /// The . + /// The for chaining. + [AspireExport("onConnectionStringAvailable", Description = "Subscribes a callback to the ConnectionStringAvailableEvent event of the resource.")] public static IResourceBuilder OnConnectionStringAvailable(this IResourceBuilder builder, Func callback) where T : IResourceWithConnectionString - => builder.OnEvent(callback); + => builder.OnResourceEvent(callback); /// - /// Subscribes a callback to the event within the AppHost. + /// Subscribes a callback to the event for . /// /// The resource type. /// The resource builder. /// A callback to handle the event. - /// The . + /// The for chaining. + [AspireExport("onInitializeResource", Description = "Subscribes a callback to the InitializeResourceEvent event of the resource.")] public static IResourceBuilder OnInitializeResource(this IResourceBuilder builder, Func callback) where T : IResource - => builder.OnEvent(callback); + => builder.OnResourceEvent(callback); /// - /// Subscribes a callback to the event within the AppHost. + /// Subscribes a callback to the event for . /// /// The resource type. /// The resource builder. /// A callback to handle the event. - /// The . + /// The for chaining. + [AspireExport("onResourceEndpointsAllocated", Description = "Subscribes a callback to the ResourceEndpointsAllocatedEvent event of the resource.")] public static IResourceBuilder OnResourceEndpointsAllocated(this IResourceBuilder builder, Func callback) where T : IResourceWithEndpoints - => builder.OnEvent(callback); + => builder.OnResourceEvent(callback); /// - /// Subscribes a callback to the event within the AppHost. + /// Subscribes a callback to the event for . /// /// The resource type. /// The resource builder. /// A callback to handle the event. - /// The . + /// The for chaining. + [AspireExport("onResourceReady", Description = "Subscribes a callback to the ResourceReadyEvent event of the resource.")] public static IResourceBuilder OnResourceReady(this IResourceBuilder builder, Func callback) where T : IResource - => builder.OnEvent(callback); + => builder.OnResourceEvent(callback); + + private static T OnApplicationEvent(this T builder, Func callback) + where T : IDistributedApplicationBuilder + where TEvent : IDistributedApplicationEvent + { + builder.Eventing.Subscribe(callback); + return builder; + } - private static IResourceBuilder OnEvent(this IResourceBuilder builder, Func callback) + private static IResourceBuilder OnResourceEvent(this IResourceBuilder builder, Func callback) where TResource : IResource where TEvent : IDistributedApplicationResourceEvent { diff --git a/tests/Aspire.Hosting.Tests/Eventing/DistributedApplicationBuilderEventingTests.cs b/tests/Aspire.Hosting.Tests/Eventing/DistributedApplicationBuilderEventingTests.cs index 54b5807b4c0..fe5efb094cf 100644 --- a/tests/Aspire.Hosting.Tests/Eventing/DistributedApplicationBuilderEventingTests.cs +++ b/tests/Aspire.Hosting.Tests/Eventing/DistributedApplicationBuilderEventingTests.cs @@ -3,6 +3,7 @@ using Aspire.TestUtilities; using Aspire.Hosting.Eventing; +using Aspire.Hosting.Publishing; using Aspire.Hosting.Utils; using Microsoft.AspNetCore.InternalTesting; using Microsoft.Extensions.DependencyInjection; @@ -335,6 +336,134 @@ public async Task ResourceStoppedEventFiresWhenResourceStops() await resourceStoppedTcs.Task.DefaultTimeout(); } + [Fact] + public async Task OnBeforeStartSubscribesToBeforeStartEvent() + { + var eventFired = new ManualResetEventSlim(); + + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + builder.OnBeforeStart((e, ct) => + { + Assert.NotNull(e.Services); + Assert.NotNull(e.Model); + eventFired.Set(); + return Task.CompletedTask; + }); + + using var app = builder.Build(); + await app.StartAsync(); + + var fired = eventFired.Wait(TimeSpan.FromSeconds(10)); + Assert.True(fired); + + await app.StopAsync(); + } + + [Fact] + public async Task OnAfterResourcesCreatedSubscribesToAfterResourcesCreatedEvent() + { + var eventFired = new ManualResetEventSlim(); + + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + builder.OnAfterResourcesCreated((e, ct) => + { + Assert.NotNull(e.Services); + Assert.NotNull(e.Model); + eventFired.Set(); + return Task.CompletedTask; + }); + + using var app = builder.Build(); + await app.StartAsync(); + + var fired = eventFired.Wait(TimeSpan.FromSeconds(10)); + Assert.True(fired); + + await app.StopAsync(); + } + + [Fact] + public async Task OnBeforePublishSubscribesToBeforePublishEvent() + { + var eventFired = false; + + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + builder.OnBeforePublish((e, ct) => + { + Assert.NotNull(e.Model); + eventFired = true; + return Task.CompletedTask; + }); + + using var app = builder.Build(); + var eventing = app.Services.GetRequiredService(); + + // Manually publish the event to verify subscription + var testEvent = new BeforePublishEvent(app.Services, new([])); + await eventing.PublishAsync(testEvent, CancellationToken.None); + + Assert.True(eventFired); + } + + [Fact] + public async Task OnAfterPublishSubscribesToAfterPublishEvent() + { + var eventFired = false; + + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + builder.OnAfterPublish((e, ct) => + { + Assert.NotNull(e.Model); + eventFired = true; + return Task.CompletedTask; + }); + + using var app = builder.Build(); + var eventing = app.Services.GetRequiredService(); + + // Manually publish the event to verify subscription + var testEvent = new AfterPublishEvent(app.Services, new([])); + await eventing.PublishAsync(testEvent, CancellationToken.None); + + Assert.True(eventFired); + } + + [Fact] + public void OnBeforeStartReturnsBuilderForChaining() + { + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + var result = builder.OnBeforeStart((e, ct) => Task.CompletedTask); + + Assert.Same(builder, result); + } + + [Fact] + public void OnAfterResourcesCreatedReturnsBuilderForChaining() + { + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + var result = builder.OnAfterResourcesCreated((e, ct) => Task.CompletedTask); + + Assert.Same(builder, result); + } + + [Fact] + public void OnBeforePublishReturnsBuilderForChaining() + { + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + var result = builder.OnBeforePublish((e, ct) => Task.CompletedTask); + + Assert.Same(builder, result); + } + + [Fact] + public void OnAfterPublishReturnsBuilderForChaining() + { + using var builder = TestDistributedApplicationBuilder.Create(testOutputHelper); + var result = builder.OnAfterPublish((e, ct) => Task.CompletedTask); + + Assert.Same(builder, result); + } + public class DummyEvent : IDistributedApplicationEvent { } diff --git a/tests/Aspire.Hosting.Tests/OperationModesTests.cs b/tests/Aspire.Hosting.Tests/OperationModesTests.cs index 552a44aa751..a26a877a7e4 100644 --- a/tests/Aspire.Hosting.Tests/OperationModesTests.cs +++ b/tests/Aspire.Hosting.Tests/OperationModesTests.cs @@ -20,7 +20,7 @@ public async Task VerifyBackwardsCompatibleRunModeInvocation() using var builder = TestDistributedApplicationBuilder.Create().WithTestAndResourceLogging(outputHelper); var tcs = new TaskCompletionSource(); - builder.Eventing.Subscribe((e, ct) => { + builder.OnAfterResourcesCreated((e, ct) => { var context = e.Services.GetRequiredService(); tcs.SetResult(context); return Task.CompletedTask;