Поделиться через


События в .NET.NET Aspire

В .NET.NET Aspireвзаимодействие с событиями позволяет публиковать и подписываться на события в течение различных циклов жизни хоста приложения . Организация событий более гибкая, чем события жизненного цикла. Обе системы позволяют выполнять произвольный код во время обратных вызовов событий, но eventing обеспечивает более точное управление временем выполнения событий, их публикацией и поддерживает создание пользовательских событий.

Механизмы событий в .NET.NET Aspire являются частью пакета NuGet 📦Aspire.Hosting. Этот пакет предоставляет набор интерфейсов и классов в пространстве имен Aspire.Hosting.Eventing, которые вы используете для публикации и подписки на события в проекте узла приложения .NET.NET Aspire. Обработка событий ограничивается только узлом приложения и его внутренними ресурсами.

В этой статье вы узнаете, как использовать функции событий в .NET.NET Aspire.

Событие хоста приложения

Следующие события доступны в узле приложения и выполняются в следующем порядке:

  1. BeforeStartEvent: Это событие возникает перед запуском хоста приложения.
  2. AfterEndpointsAllocatedEvent: Это событие возникает после того, как хост приложения выделил конечные точки.
  3. AfterResourcesCreatedEvent: Это событие возникает после того, как узлом приложения созданы ресурсы.

Все предыдущие события аналогичны жизненным циклам хоста приложения . То есть реализация IDistributedApplicationLifecycleHook может обрабатывать эти события так же. Однако с помощью API событий вы можете запустить произвольный код, когда эти события создаются, и даже определить пользовательские события — любое событие, реализующее интерфейс IDistributedApplicationEvent.

Подписка на события хоста приложения

Чтобы подписаться на встроенные события узла приложения, используйте API событий. Получив экземпляр распределенного построителя приложений, перейдите к свойству IDistributedApplicationBuilder.Eventing и вызовите API Subscribe<T>(Func<T,CancellationToken,Task>). Рассмотрим следующий пример файла Program.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>(
    (@event, cancellationToken) =>
    {
        // The event doesn't expose an IServiceProvider, just write to the console.
        Console.WriteLine($"""
                    3. '{@event.Resource.Name}' ResourceEndpointsAllocatedEvent
            """);

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

        logger.LogInformation("2. AfterEndpointsAllocatedEvent");

        return Task.CompletedTask;
    });

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

        logger.LogInformation("4. AfterResourcesCreatedEvent");

        return Task.CompletedTask;
    });

builder.Build().Run();

Предыдущий код основан на начальном шаблоне с добавлением вызовов к API Subscribe. API Subscribe<T> возвращает экземпляр DistributedApplicationEventSubscription, который можно использовать для отмены подписки от события. Обычно отказываются от возвращаемых подписок, поскольку нет необходимости отменять подписки на события, так как все приложение отключается при завершении работы хоста приложения.

При запуске хоста приложения к моменту отображения панели мониторинга .NET.NET Aspire вы должны увидеть следующие журнальные данные в консоли:

info: Program[0]
      1. BeforeStartEvent
info: Aspire.Hosting.DistributedApplication[0]
      Aspire version: 9.3.0-preview.1.25262.2+6d54dc081cd2e7ea435e33f7c0e62ff6946ae66d
info: Aspire.Hosting.DistributedApplication[0]
      Distributed application starting.
info: Aspire.Hosting.DistributedApplication[0]
      Application host directory is: ../AspireApp/AspireApp.AppHost
info: Program[0]
      2. AfterEndpointsAllocatedEvent
        3. 'aspire-dashboard' ResourceEndpointsAllocatedEvent
        3. 'cache' ResourceEndpointsAllocatedEvent
        3. 'apiservice' ResourceEndpointsAllocatedEvent
        3. 'webfrontend' 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]
      4. AfterResourcesCreatedEvent
info: Aspire.Hosting.DistributedApplication[0]
      Distributed application started. Press Ctrl+C to shut down.

Выходные данные журнала подтверждают, что обработчики событий выполняются в порядке событий жизненного цикла узла приложения. Порядок подписки не влияет на последовательность выполнения. Сначала активируется BeforeStartEvent, затем AfterEndpointsAllocatedEvent, потом у каждого ресурса инициируется событие ResourceEndpointsAllocatedEvent, и наконец, AfterResourcesCreatedEvent.

Управление событиями ресурсов

Помимо событий узла приложения, вы также можете подписаться на ресурсные события. События ресурсов специфично возникают для отдельного ресурса. События ресурсов определяются как реализации интерфейса IDistributedApplicationResourceEvent. Следующие события ресурсов доступны в указанном порядке:

  1. InitializeResourceEvent: Вызывается оркестраторами, чтобы дать сигнал ресурсам, что они должны инициализироваться.
  2. ConnectionStringAvailableEvent: Выдается сообщение о том, что строка подключения стала доступной для ресурса.
  3. BeforeResourceStartedEvent. Вызывается перед запуском нового ресурса, управляемого оркестратором.
  4. ResourceReadyEvent: возникает при первоначальном переходе ресурса в готовое состояние.

Подписка на события ресурсов

Чтобы подписаться на события ресурсов, используйте API событий. Получив экземпляр распределенного построителя приложений, перейдите к свойству IDistributedApplicationBuilder.Eventing и вызовите API Subscribe<T>(IResource, Func<T,CancellationToken,Task>). Рассмотрим следующий пример файла Program.cs узла приложения:

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

var builder = DistributedApplication.CreateBuilder(args);

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

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

        logger.LogInformation("4. ResourceReadyEvent");

        return Task.CompletedTask;
    });

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

        logger.LogInformation("1. InitializeResourceEvent");

        return Task.CompletedTask;
    });

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

        logger.LogInformation("3. BeforeResourceStartedEvent");

        return Task.CompletedTask;
    });

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

        logger.LogInformation("2. ConnectionStringAvailableEvent");

        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, ResourceReadyEvent, ConnectionStringAvailableEvent и BeforeResourceStartedEvent, происходящие в ресурсе cache. Когда вызывается AddRedis, возвращается IResourceBuilder<T>, в котором T представляет собой RedisResource. Построитель ресурсов предоставляет ресурс в качестве свойства IResourceBuilder<T>.Resource. Затем указанный ресурс передается в API Subscribe, чтобы подписаться на события ресурса.

При запуске хоста приложения к моменту отображения панели мониторинга .NET.NET Aspire вы должны увидеть следующие журнальные данные в консоли:

info: Aspire.Hosting.DistributedApplication[0]
      Aspire version: 9.3.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. InitializeResourceEvent
info: Program[0]
      2. ConnectionStringAvailableEvent
info: Program[0]
      3. BeforeResourceStartedEvent
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]
      4. ResourceReadyEvent
info: Aspire.Hosting.DistributedApplication[0]
      Distributed application started. Press Ctrl+C to shut down.

Заметка

Некоторые события блокируются. Например, при публикации BeforeResourceStartEvent запуск ресурса будет заблокирован до завершения выполнения всех подписок на данное событие. От того, является ли событие блокирующим, зависит то, как оно опубликовано (см. следующий раздел).

Публикация событий

При подписке на любые встроенные события вам не нужно публиковать событие самостоятельно, так как оркестратор узла приложений управляет публикацией встроенных событий от вашего имени. Однако можно опубликовать пользовательские события с помощью API событий. Чтобы опубликовать событие, сначала необходимо определить событие как реализацию интерфейса IDistributedApplicationEvent или IDistributedApplicationResourceEvent. Необходимо определить, какой интерфейс следует реализовать на основе того, является ли событие глобальным событием хоста приложения или событием, специфичным для ресурса.

Затем вы можете подписаться и опубликовать событие, вызвав любой из следующих API:

Предоставьте EventDispatchBehavior

При отправке событий можно управлять отправкой событий подписчикам. Поведение диспетчеризации событий указывается в перечислении EventDispatchBehavior. Доступны следующие действия:

Поведение по умолчанию — это EventDispatchBehavior.BlockingSequential. Чтобы переопределить это поведение, при вызове API публикации, например PublishAsync, укажите требуемое поведение в качестве аргумента.

События жизненного цикла узла приложений

Как вы уже видели, eventing — это самый гибкий подход. Однако в этом разделе вы узнаете об альтернативных событиях жизненного цикла.

Хост приложения .NET.NET Aspire предоставляет несколько этапов жизненного цикла, в которые можно интегрироваться, реализуя интерфейс IDistributedApplicationLifecycleHook. Доступны следующие методы жизненного цикла:

Заказ Метод Описание
1 BeforeStartAsync Выполняется до запуска распределенного приложения.
2 AfterEndpointsAllocatedAsync Выполняется после того, как оркестратор выделяет конечные точки для ресурсов в модели приложения.
3 AfterResourcesCreatedAsync Выполняется после того, как ресурс был создан оркестратором.

Зарегистрируйте хук жизненного цикла

Чтобы зарегистрировать хук жизненного цикла, реализуйте интерфейс IDistributedApplicationLifecycleHook и зарегистрируйте хук на хосте приложения с помощью API AddLifecycleHook.

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;
    }
}

Предыдущий код:

  • Реализует интерфейс IDistributedApplicationLifecycleHook в виде LifecycleLogger.
  • Регистрирует хук жизненного цикла с хостом приложения через API AddLifecycleHook.
  • Записывает сообщение для всех событий.

При запуске этого хоста приложения, хук жизненного цикла выполняется для каждого события. Создаются следующие выходные данные:

info: LifecycleLogger[0]
      1. BeforeStartAsync
info: Aspire.Hosting.DistributedApplication[0]
      Aspire version: 9.3.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.

Предпочтительный способ подключения к жизненному циклу узла приложения — использовать API событий. Дополнительные сведения см. в разделе "События узла приложений".

См. также