Compartilhar via


Comandos de recursos personalizados no .NET.NET Aspire

Cada recurso no modelo de aplicativo é representado como um e, quando adicionado aodo construtor de aplicativos distribuído , é o parâmetro de tipo genérico da interface . Você usa o construtor de recursos API para encadear chamadas, configurar o recurso subjacente e, em algumas situações, talvez queira adicionar comandos personalizados ao recurso. Alguns cenários comuns para criar um comando personalizado podem ser ao executar migrações de banco de dados ou realizando a propagação/redefinição de um banco de dados. Neste artigo, você aprenderá a adicionar um comando personalizado a um recurso de Redis que limpa o cache.

Importante

Esses comandos .NET.NET Aspire dashboard só estão disponíveis ao executar o painel localmente. Eles não estão disponíveis ao executar o painel no Azure Container Apps.

Adicionar comandos personalizados a um recurso

Comece criando um novo aplicativo inicial .NET.NET Aspire a partir dos modelos disponíveis. Para criar a solução com base nesse modelo, siga o início rápido do : criar sua primeira solução .NET.NET Aspire. Depois de criar essa solução, adicione uma nova classe chamada RedisResourceBuilderExtensions.cs ao projeto de host do aplicativo . Substitua o conteúdo do arquivo pelo seguinte código:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using StackExchange.Redis;

namespace Aspire.Hosting;

internal static class RedisResourceBuilderExtensions
{
    public static IResourceBuilder<RedisResource> WithClearCommand(
        this IResourceBuilder<RedisResource> builder)
    {
        builder.WithCommand(
            name: "clear-cache",
            displayName: "Clear Cache",
            executeCommand: context => OnRunClearCacheCommandAsync(builder, context),
            updateState: OnUpdateResourceState,
            iconName: "AnimalRabbitOff",
            iconVariant: IconVariant.Filled);

        return builder;
    }

    private static async Task<ExecuteCommandResult> OnRunClearCacheCommandAsync(
        IResourceBuilder<RedisResource> builder,
        ExecuteCommandContext context)
    {
        var connectionString = await builder.Resource.GetConnectionStringAsync() ??
            throw new InvalidOperationException(
                $"Unable to get the '{context.ResourceName}' connection string.");

        await using var connection = ConnectionMultiplexer.Connect(connectionString);

        var database = connection.GetDatabase();

        await database.ExecuteAsync("FLUSHALL");

        return CommandResults.Success();
    }

    private static ResourceCommandState OnUpdateResourceState(
        UpdateCommandStateContext context)
    {
        var logger = context.ServiceProvider.GetRequiredService<ILogger<Program>>();

        if (logger.IsEnabled(LogLevel.Information))
        {
            logger.LogInformation(
                "Updating resource state: {ResourceSnapshot}",
                context.ResourceSnapshot);
        }

        return context.ResourceSnapshot.HealthStatus is HealthStatus.Healthy
            ? ResourceCommandState.Enabled
            : ResourceCommandState.Disabled;
    }
}

O código anterior:

  • Compartilha o namespace Aspire.Hosting para que ele fique visível para o projeto de host do aplicativo.
  • É um static class para que possa conter métodos de extensão.
  • Ele define um único método de extensão chamado WithClearCommand, estendendo a interface IResourceBuilder<RedisResource>.
  • O método WithClearCommand registra um comando chamado clear-cache que limpa o cache do recurso Redis.
  • O método WithClearCommand retorna a instância IResourceBuilder<RedisResource> para permitir o encadeamento.

A API WithCommand adiciona as anotações apropriadas ao recurso, que são consumidas no painel .NET.NET Aspire. O painel usa essas anotações para renderizar o comando na interface do usuário. Antes de ir muito longe nesses detalhes, vamos garantir que você primeiro entenda os parâmetros do método WithCommand:

  • name: o nome do comando a ser invocado.
  • displayName: o nome do comando a ser exibido no painel.
  • executeCommand: o Func<ExecuteCommandContext, Task<ExecuteCommandResult>> a ser executado quando o comando é invocado, local onde a lógica do comando é implementada.
  • updateState: O callback Func<UpdateCommandStateContext, ResourceCommandState> é invocado para determinar o estado de habilitação do comando, que é usado para habilitar ou desabilitar o comando no dashboard.
  • iconName: o nome do ícone a ser exibido no painel. O ícone é opcional, mas quando você o fornecer, ele deve ter um nome válido de ícone Fluent UI .
  • iconVariant: a variante do ícone a ser exibida no painel, as opções válidas são Regular (padrão) ou Filled.

Executar a lógica do comando

O delegado executeCommand é onde a lógica de comando é implementada. Esse parâmetro é definido como um Func<ExecuteCommandContext, Task<ExecuteCommandResult>>. O ExecuteCommandContext fornece as seguintes propriedades:

  • ExecuteCommandContext.ServiceProvider: a instância de IServiceProvider usada para resolver serviços.
  • ExecuteCommandContext.ResourceName: o nome da instância de recurso na qual o comando está sendo executado.
  • ExecuteCommandContext.CancellationToken: o CancellationToken usado para cancelar a execução do comando.

No exemplo anterior, o delegado executeCommand é implementado como um método async que limpa o cache do recurso de Redis. Ele delega para uma função de escopo de classe privada chamada OnRunClearCacheCommandAsync para realizar a limpeza efetiva do cache. Considere o seguinte código:

private static async Task<ExecuteCommandResult> OnRunClearCacheCommandAsync(
    IResourceBuilder<RedisResource> builder,
    ExecuteCommandContext context)
{
    var connectionString = await builder.Resource.GetConnectionStringAsync() ??
        throw new InvalidOperationException(
            $"Unable to get the '{context.ResourceName}' connection string.");

    await using var connection = ConnectionMultiplexer.Connect(connectionString);

    var database = connection.GetDatabase();

    await database.ExecuteAsync("FLUSHALL");

    return CommandResults.Success();
}

O código anterior:

  • Recupera a cadeia de conexão do recurso Redis.
  • Conecta-se à instância de Redis.
  • Obtém a instância do banco de dados.
  • Executa o comando FLUSHALL para limpar o cache.
  • Retorna uma instância de CommandResults.Success() para indicar que o comando foi bem-sucedido.

Atualizar lógica de estado do comando

O delegado updateState é onde o estado do comando é determinado. Esse parâmetro é definido como um Func<UpdateCommandStateContext, ResourceCommandState>. O UpdateCommandStateContext fornece as seguintes propriedades:

  • UpdateCommandStateContext.ServiceProvider: a instância de IServiceProvider usada para resolver serviços.
  • UpdateCommandStateContext.ResourceSnapshot: o instantâneo da instância do recurso na qual o comando está sendo executado atualmente.

O instantâneo imutável é uma instância de CustomResourceSnapshot, que expõe todos os tipos de detalhes valiosos sobre a instância de recurso. Considere o seguinte código:

private static ResourceCommandState OnUpdateResourceState(
    UpdateCommandStateContext context)
{
    var logger = context.ServiceProvider.GetRequiredService<ILogger<Program>>();

    if (logger.IsEnabled(LogLevel.Information))
    {
        logger.LogInformation(
            "Updating resource state: {ResourceSnapshot}",
            context.ResourceSnapshot);
    }

    return context.ResourceSnapshot.HealthStatus is HealthStatus.Healthy
        ? ResourceCommandState.Enabled
        : ResourceCommandState.Disabled;
}

O código anterior:

  • Recupera a instância do registrador do provedor de serviços.
  • Registra os detalhes do instantâneo do recurso.
  • Retorna ResourceCommandState.Enabled se o recurso estiver íntegro; caso contrário, retorna ResourceCommandState.Disabled.

Testar o comando personalizado

Para testar o comando personalizado, atualize o arquivo Program.cs do projeto de host do aplicativo para incluir o seguinte código:

var builder = DistributedApplication.CreateBuilder(args);

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

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

O código anterior chama o método de extensão WithClearCommand para adicionar o comando personalizado ao recurso Redis. Execute o aplicativo e navegue até o painel .NET.NET Aspire. Você deve ver o comando personalizado listado no recurso Redis. Na página Recursos do painel, selecione o botão de mais opções na coluna Ações.

.NET Aspire painel: Redis recurso de cache com o comando personalizado exibido.

A imagem anterior mostra o comando Limpar cache que foi adicionado ao recurso Redis. O ícone é exibido como um coelho risca para indicar que a velocidade do recurso dependente está sendo desmarcada.

Selecione o comando Limpar cache para limpar o cache do recurso de Redis. O comando deve ser executado com êxito e o cache deve ser limpo:

.NET Aspire painel: Redis recurso de cache com execução de comando personalizado.

Consulte também