事件管理 Aspire

在 Aspire 中,事件功能允许在各种 AppHost 生命周期期间发布和订阅事件。 事件系统比生命周期事件更灵活。 这两者都允许在事件回调期间运行任意代码,但事件驱动可以更细致地控制事件的计时和发布,并提供对自定义事件的支持。

Aspire 的事件机制是 📦Aspire 托管 NuGet 包的一部分。 此包提供了一组位于Aspire.Hosting.Eventing命名空间中的接口和类,用于在Aspire AppHost 项目中发布和订阅事件。 事件范围限定为 AppHost 本身及其内部资源。

在本文中,您将学习如何在 Aspire 中使用事件功能。

AppHost 事件处理

以下事件在 AppHost 中可用,并按以下顺序发生:

  1. BeforeStartEvent:此事件在 AppHost 启动之前被引发。
  2. ResourceEndpointsAllocatedEvent:分配终结点后,将为每个资源引发此事件。
  3. AfterResourcesCreatedEvent:此事件在 AppHost 创建资源后引发。

上述所有事件都类似于 AppHost 生命周期。 也就是说,IDistributedApplicationLifecycleHook 的实现可以同样地处理这些事件。 但是,使用事件 API,可以在触发这些事件时运行任意代码,甚至定义自定义事件——任何实现 IDistributedApplicationEvent 接口的事件。

订阅 AppHost 事件

若要订阅内置的 AppHost 事件,请使用事件 API。 获得分布式应用程序生成器实例后,请继续访问 IDistributedApplicationBuilder.Eventing 属性并调用 Subscribe<T>(Func<T,CancellationToken,Task>) API。 请考虑以下示例 AppHost AppHost.cs 文件:

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<ResourceEndpointsAllocatedEvent>(
    static (@event, cancellationToken) =>
    {
        var logger = @event.Services.GetRequiredService<ILogger<Program>>();

        logger.LogInformation("2. \"{ResourceName}\" ResourceEndpointsAllocatedEvent", @event.Resource.Name);

        return Task.CompletedTask;
    });

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

        logger.LogInformation("1. BeforeStartEvent");

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

上述代码基于初学者模板,并添加了对 Subscribe API 的调用。 Subscribe<T> API 返回可用于取消订阅事件的 DistributedApplicationEventSubscription 实例。 通常放弃返回的订阅,因为通常不需要取消订阅事件,因为当 AppHost 关闭时整个应用被拆毁。

当您运行 AppHost 时,仪表板被显示 Aspire 之后,您应能在控制台中看到以下日志输出:

info: Program[0]
      1. BeforeStartEvent
info: Aspire.Hosting.DistributedApplication[0]
      Aspire version: 9.4.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. "cache" ResourceEndpointsAllocatedEvent
info: Program[0]
      2. "apiservice" ResourceEndpointsAllocatedEvent
info: Program[0]
      2. "webfrontend" ResourceEndpointsAllocatedEvent
info: Program[0]
      2. "aspire-dashboard" ResourceEndpointsAllocatedEvent
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.

日志输出确认事件处理程序按 AppHost 生命周期事件的顺序执行。 订阅顺序不会影响执行顺序。 首先触发 BeforeStartEvent,然后依次触发每个资源的 ResourceEndpointsAllocatedEvent,最后触发 AfterResourcesCreatedEvent

资源事件管理

除了 AppHost 事件,还可以订阅资源事件。 资源事件是特定于单个资源触发的。 资源事件定义为 IDistributedApplicationResourceEvent 接口的实现。 以下资源事件按列出的顺序可用:

  1. InitializeResourceEvent:由协调器引发,以向资源发出信号,指示它们应自行初始化。
  2. ResourceEndpointsAllocatedEvent:当编排器为资源分配终结点时引发。
  3. ConnectionStringAvailableEvent:当连接字符串可用于资源时触发。
  4. BeforeResourceStartedEvent:在业务流程协调程序启动新资源之前引发。
  5. ResourceReadyEvent:当资源最初转换为就绪状态时触发。

订阅资源事件

若要订阅资源事件,请使用便捷的扩展方法。On* 获得分布式应用程序生成器实例和资源生成器后,使用该实例并链式调用所需的 On* 事件 API。 请考虑以下示例 AppHost.cs 文件:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

var builder = DistributedApplication.CreateBuilder(args);

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

cache.OnResourceReady(static (resource, @event, cancellationToken) =>
    {
        var logger = @event.Services.GetRequiredService<ILogger<Program>>();

        logger.LogInformation("5. OnResourceReady");

        return Task.CompletedTask;
    });

cache.OnInitializeResource(
    static (resource, @event, cancellationToken) =>
    {
        var logger = @event.Services.GetRequiredService<ILogger<Program>>();

        logger.LogInformation("1. OnInitializeResource");

        return Task.CompletedTask;
    });

cache.OnBeforeResourceStarted(
    static (resource, @event, cancellationToken) =>
    {
        var logger = @event.Services.GetRequiredService<ILogger<Program>>();

        logger.LogInformation("4. OnBeforeResourceStarted");


        return Task.CompletedTask;
    });

cache.OnResourceEndpointsAllocated(
    static (resource, @event, cancellationToken) =>
    {
        var logger = @event.Services.GetRequiredService<ILogger<Program>>();

        logger.LogInformation("2. OnResourceEndpointsAllocated");

        return Task.CompletedTask;
    });

cache.OnConnectionStringAvailable(
    static (resource, @event, cancellationToken) =>
    {
        var logger = @event.Services.GetRequiredService<ILogger<Program>>();

        logger.LogInformation("3. OnConnectionStringAvailable");

        return Task.CompletedTask;
    });

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

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

builder.Build().Run();

前面的代码订阅InitializeResourceEvent资源上的 ResourceReadyEventResourceEndpointsAllocatedEventConnectionStringAvailableEventBeforeResourceStartedEventcache事件。 调用 AddRedis 时,它将返回一个 IResourceBuilder<T>,其中 TRedisResource。 将方法 On* 链式调用以订阅事件。 这些 On* 方法返回相同的 IResourceBuilder<T> 实例,因此可以链接多个调用:

运行 AppHost 时,当显示仪表板 Aspire 时,您应在控制台中看到以下日志输出:

info: Aspire.Hosting.DistributedApplication[0]
      Aspire version: 9.4.0
info: Aspire.Hosting.DistributedApplication[0]
      Distributed application starting.
info: Aspire.Hosting.DistributedApplication[0]
      Application host directory is: ../AspireApp/AspireApp.AppHost
info: Program[0]
      1. OnInitializeResource
info: Program[0]
      2. OnResourceEndpointsAllocated
info: Program[0]
      3. OnConnectionStringAvailable
info: Program[0]
      4. OnBeforeResourceStarted
info: Aspire.Hosting.DistributedApplication[0]
      Now listening on: https://localhost:17222
info: Aspire.Hosting.DistributedApplication[0]
      Login to the dashboard at https://localhost:17222/login?t=<YOUR_TOKEN>
info: Program[0]
      5. OnResourceReady
info: Aspire.Hosting.DistributedApplication[0]
      Distributed application started. Press Ctrl+C to shut down.

Note

某些事件阻止执行。 例如,当 BeforeResourceStartedEvent 被发布时,该资源的启动过程会被阻塞,直到在给定资源上与该事件相关的所有订阅完成执行。 事件是否会被阻止,取决于您发布它的方式(请参阅以下部分)。

发布事件

订阅任何内置事件时,无需自行发布事件,因为 AppHost 业务流程协调程序会管理以代表你发布内置事件。 但是,可以使用事件 API 发布自定义事件。 若要发布事件,必须首先将事件定义为 IDistributedApplicationEventIDistributedApplicationResourceEvent 接口的实现。 需要根据事件是全局 AppHost 事件还是特定于资源的事件来确定要实现的接口。

然后,可以通过调用以下任一 API 来订阅和发布事件:

提供编号EventDispatchBehavior

事件被发送时,您可以控制事件如何发送给订阅者。 使用 EventDispatchBehavior 枚举来定义事件调度行为。 有以下行为可用:

默认行为为 EventDispatchBehavior.BlockingSequential。 若要覆盖此行为,在调用发布 API(如 PublishAsync)时,请将期望的行为作为参数提供。

AppHost 生命周期事件

Eventing提供最大的灵活性。 但是,本部分介绍替代方法:生命周期事件。

Aspire AppHost 公开了几个生命周期,可以通过实现IDistributedApplicationLifecycleHook接口来挂钩。 可以使用以下生命周期方法:

Order Method Description
1 BeforeStartAsync 在分布式应用程序启动之前运行。
2 AfterEndpointsAllocatedAsync 在协调器为应用程序模型中的资源分配端点之后运行。
3 AfterResourcesCreatedAsync 在业务流程协调程序创建资源后运行。

注册生命周期钩子

若要注册生命周期挂钩,请实现 IDistributedApplicationLifecycleHook 接口,并使用 AddLifecycleHook API 向 AppHost 注册挂钩:

using Aspire.Hosting.Lifecycle;
using Microsoft.Extensions.Logging;

var builder = DistributedApplication.CreateBuilder(args);

builder.Services.AddLifecycleHook<LifecycleLogger>();

builder.Build().Run();

internal sealed class LifecycleLogger(ILogger<LifecycleLogger> logger)
    : IDistributedApplicationLifecycleHook
{
    public Task BeforeStartAsync(
        DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
    {
        logger.LogInformation("1. BeforeStartAsync");
        return Task.CompletedTask;
    }

    public Task AfterEndpointsAllocatedAsync(
        DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
    {
        logger.LogInformation("2. AfterEndpointsAllocatedAsync");
        return Task.CompletedTask;
    }

    public Task AfterResourcesCreatedAsync(
        DistributedApplicationModel appModel, CancellationToken cancellationToken = default)
    {
        logger.LogInformation("3. AfterResourcesCreatedAsync");
        return Task.CompletedTask;
    }
}

前面的代码:

当运行此 AppHost 时,将对每个事件执行生命周期钩子。 生成以下输出:

info: LifecycleLogger[0]
      1. BeforeStartAsync
info: Aspire.Hosting.DistributedApplication[0]
      Aspire version: 9.4.0
info: Aspire.Hosting.DistributedApplication[0]
      Distributed application starting.
info: Aspire.Hosting.DistributedApplication[0]
      Application host directory is: ../AspireApp/AspireApp.AppHost
info: LifecycleLogger[0]
      2. AfterEndpointsAllocatedAsync
info: Aspire.Hosting.DistributedApplication[0]
      Now listening on: https://localhost:17043
info: Aspire.Hosting.DistributedApplication[0]
      Login to the dashboard at https://localhost:17043/login?t=<YOUR_TOKEN>
info: LifecycleLogger[0]
      3. AfterResourcesCreatedAsync
info: Aspire.Hosting.DistributedApplication[0]
      Distributed application started. Press Ctrl+C to shut down.

连接到 AppHost 生命周期的首选方法是使用事件 API。 有关详细信息,请参阅 AppHost 事件

另请参阅