File: Ats\EventingExports.cs
Web Access
Project: src\src\Aspire.Hosting\Aspire.Hosting.csproj (Aspire.Hosting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.ApplicationModel;
using Aspire.Hosting.Eventing;
using Aspire.Hosting.Lifecycle;
using Aspire.Hosting.Publishing;
using Microsoft.Extensions.DependencyInjection;
 
namespace Aspire.Hosting.Ats;
 
/// <summary>
/// ATS exports for distributed application eventing operations.
/// </summary>
internal static class EventingExports
{
    /// <summary>
    /// Adds an ATS-friendly eventing subscriber callback to the distributed-application builder.
    /// </summary>
    /// <param name="builder">The distributed-application builder.</param>
    /// <param name="subscribe">The callback that registers the event subscriptions.</param>
    [AspireExport]
    public static void AddEventingSubscriber(this IDistributedApplicationBuilder builder, Func<EventingSubscriberRegistrationContext, Task> subscribe)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(subscribe);
 
        builder.Services.AddSingleton<IDistributedApplicationEventingSubscriber>(new CallbackEventingSubscriber(subscribe));
    }
 
    /// <summary>
    /// Attempts to add an ATS-friendly eventing subscriber callback to the distributed-application builder.
    /// </summary>
    /// <param name="builder">The distributed-application builder.</param>
    /// <param name="subscribe">The callback that registers the event subscriptions.</param>
    [AspireExport]
    public static void TryAddEventingSubscriber(this IDistributedApplicationBuilder builder, Func<EventingSubscriberRegistrationContext, Task> subscribe)
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(subscribe);
 
        if (builder.Services.Any(descriptor => descriptor.ServiceType == typeof(IDistributedApplicationEventingSubscriber) &&
                                               descriptor.ImplementationInstance is CallbackEventingSubscriber existing &&
                                               existing.Matches(subscribe)))
        {
            return;
        }
 
        builder.Services.AddSingleton<IDistributedApplicationEventingSubscriber>(new CallbackEventingSubscriber(subscribe));
    }
 
    /// <summary>
    /// Gets the distributed application eventing service from the service provider.
    /// </summary>
    /// <param name="serviceProvider">The service provider handle.</param>
    /// <returns>The distributed application eventing handle.</returns>
    [AspireExport]
    internal static IDistributedApplicationEventing GetEventing(this IServiceProvider serviceProvider)
    {
        ArgumentNullException.ThrowIfNull(serviceProvider);
 
        return serviceProvider.GetRequiredService<IDistributedApplicationEventing>();
    }
 
    /// <summary>
    /// Subscribes to the BeforeResourceStarted event.
    /// </summary>
    /// <param name="builder">The resource builder.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The resource builder.</returns>
    [AspireExport]
    internal static IResourceBuilder<T> OnBeforeResourceStarted<T>(this IResourceBuilder<T> builder, Func<BeforeResourceStartedEvent, Task> callback)
        where T : IResource
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(callback);
 
        return DistributedApplicationEventingExtensions.OnBeforeResourceStarted(builder, (_, @event, _) => callback(@event));
    }
 
    /// <summary>
    /// Subscribes to the ResourceStopped event.
    /// </summary>
    /// <param name="builder">The resource builder.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The resource builder.</returns>
    [AspireExport]
    internal static IResourceBuilder<T> OnResourceStopped<T>(this IResourceBuilder<T> builder, Func<ResourceStoppedEvent, Task> callback)
        where T : IResource
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(callback);
 
        return DistributedApplicationEventingExtensions.OnResourceStopped(builder, (_, @event, _) => callback(@event));
    }
 
    /// <summary>
    /// Subscribes to the ConnectionStringAvailable event.
    /// </summary>
    /// <param name="builder">The resource builder.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The resource builder.</returns>
    [AspireExport]
    internal static IResourceBuilder<T> OnConnectionStringAvailable<T>(this IResourceBuilder<T> builder, Func<ConnectionStringAvailableEvent, Task> callback)
        where T : IResourceWithConnectionString
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(callback);
 
        return DistributedApplicationEventingExtensions.OnConnectionStringAvailable(builder, (_, @event, _) => callback(@event));
    }
 
    /// <summary>
    /// Subscribes to the InitializeResource event.
    /// </summary>
    /// <param name="builder">The resource builder.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The resource builder.</returns>
    [AspireExport]
    internal static IResourceBuilder<T> OnInitializeResource<T>(this IResourceBuilder<T> builder, Func<InitializeResourceEvent, Task> callback)
        where T : IResource
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(callback);
 
        return DistributedApplicationEventingExtensions.OnInitializeResource(builder, (_, @event, _) => callback(@event));
    }
 
    /// <summary>
    /// Subscribes to the ResourceEndpointsAllocated event.
    /// </summary>
    /// <param name="builder">The resource builder.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The resource builder.</returns>
    [AspireExport]
    internal static IResourceBuilder<T> OnResourceEndpointsAllocated<T>(this IResourceBuilder<T> builder, Func<ResourceEndpointsAllocatedEvent, Task> callback)
        where T : IResourceWithEndpoints
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(callback);
 
        return DistributedApplicationEventingExtensions.OnResourceEndpointsAllocated(builder, (_, @event, _) => callback(@event));
    }
 
    /// <summary>
    /// Subscribes to the ResourceReady event.
    /// </summary>
    /// <param name="builder">The resource builder.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The resource builder.</returns>
    [AspireExport]
    internal static IResourceBuilder<T> OnResourceReady<T>(this IResourceBuilder<T> builder, Func<ResourceReadyEvent, Task> callback)
        where T : IResource
    {
        ArgumentNullException.ThrowIfNull(builder);
        ArgumentNullException.ThrowIfNull(callback);
 
        return DistributedApplicationEventingExtensions.OnResourceReady(builder, (_, @event, _) => callback(@event));
    }
 
    /// <summary>
    /// Subscribes to the BeforeStart event from an eventing subscriber registration context.
    /// </summary>
    /// <param name="context">The eventing subscriber registration context.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The event subscription.</returns>
    [AspireExport("eventingSubscriberOnBeforeStart", MethodName = "onBeforeStart")]
    public static DistributedApplicationEventSubscription OnBeforeStart(this EventingSubscriberRegistrationContext context, Func<BeforeStartEvent, Task> callback)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(callback);
 
        return context.Eventing.Subscribe<BeforeStartEvent>((@event, _) => callback(@event));
    }
 
    /// <summary>
    /// Subscribes to the BeforePublish event from an eventing subscriber registration context.
    /// </summary>
    /// <param name="context">The eventing subscriber registration context.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The event subscription.</returns>
    [AspireExport("eventingSubscriberOnBeforePublish", MethodName = "onBeforePublish")]
    public static DistributedApplicationEventSubscription OnBeforePublish(this EventingSubscriberRegistrationContext context, Func<BeforePublishEvent, Task> callback)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(callback);
 
        return context.Eventing.Subscribe<BeforePublishEvent>((@event, _) => callback(@event));
    }
 
    /// <summary>
    /// Subscribes to the AfterPublish event from an eventing subscriber registration context.
    /// </summary>
    /// <param name="context">The eventing subscriber registration context.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The event subscription.</returns>
    [AspireExport("eventingSubscriberOnAfterPublish", MethodName = "onAfterPublish")]
    public static DistributedApplicationEventSubscription OnAfterPublish(this EventingSubscriberRegistrationContext context, Func<AfterPublishEvent, Task> callback)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(callback);
 
        return context.Eventing.Subscribe<AfterPublishEvent>((@event, _) => callback(@event));
    }
 
    /// <summary>
    /// Subscribes to the AfterResourcesCreated event from an eventing subscriber registration context.
    /// </summary>
    /// <param name="context">The eventing subscriber registration context.</param>
    /// <param name="callback">The callback to invoke when the event fires.</param>
    /// <returns>The event subscription.</returns>
    [AspireExport("eventingSubscriberOnAfterResourcesCreated", MethodName = "onAfterResourcesCreated")]
    public static DistributedApplicationEventSubscription OnAfterResourcesCreated(this EventingSubscriberRegistrationContext context, Func<AfterResourcesCreatedEvent, Task> callback)
    {
        ArgumentNullException.ThrowIfNull(context);
        ArgumentNullException.ThrowIfNull(callback);
 
        return context.Eventing.Subscribe<AfterResourcesCreatedEvent>((@event, _) => callback(@event));
    }
 
    private sealed class CallbackEventingSubscriber(Func<EventingSubscriberRegistrationContext, Task> subscribe) : IDistributedApplicationEventingSubscriber
    {
        public bool Matches(Func<EventingSubscriberRegistrationContext, Task> otherSubscribe)
        {
            return subscribe == otherSubscribe;
        }
 
        public Task SubscribeAsync(IDistributedApplicationEventing eventing, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken)
        {
            return subscribe(new EventingSubscriberRegistrationContext(eventing, executionContext, cancellationToken));
        }
    }
}
 
/// <summary>
/// Context passed to ATS-friendly eventing subscriber registrations.
/// </summary>
[AspireExport(ExposeProperties = true)]
internal sealed class EventingSubscriberRegistrationContext(
    IDistributedApplicationEventing eventing,
    DistributedApplicationExecutionContext executionContext,
    CancellationToken cancellationToken)
{
    internal IDistributedApplicationEventing Eventing { get; } = eventing;
 
    /// <summary>
    /// The execution context for the AppHost invocation.
    /// </summary>
    public DistributedApplicationExecutionContext ExecutionContext { get; } = executionContext;
 
    /// <summary>
    /// The cancellation token associated with the subscriber registration.
    /// </summary>
    public CancellationToken CancellationToken { get; } = cancellationToken;
}