コンテンツにスキップ
Docs Try Aspire
Docs Try

カスタム リソース コマンド

Aspire アプリ モデル内の各リソースは IResource として表現され、分散アプリケーション ビルダーに追加されると、IResourceBuilder<T> インターフェイスのジェネリック型パラメーターになります。resource builder API を使って呼び出しを連鎖し、基になるリソースを構成できます。状況によっては、そのリソースにカスタム コマンドを追加したいことがあります。カスタム コマンドを作成する一般的なシナリオには、データベース移行の実行やデータベースのシード/リセットなどがあります。この記事では、キャッシュをクリアする Redis リソースにカスタム コマンドを追加する方法を学びます。

リソースにカスタム コマンドを追加する

Section titled “リソースにカスタム コマンドを追加する”

Aspire AppHost プロジェクトから開始します。以下のウォークスルーでは、Redis リソースに clear-cache コマンドを登録します。C# 版は登録をラップする拡張メソッドを使用し、TypeScript 版は管理エンドポイントを公開する Node サービスに対してコマンドをインライン登録します。どちらのフローでも、同じダッシュボード/CLI 体験を得られます。

AppHost プロジェクトに RedisResourceBuilderExtensions.cs という新しいクラスを追加し、内容を次のコードに置き換えます。

RedisResourceBuilderExtensions.cs
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)
{
var commandOptions = new CommandOptions
{
UpdateState = OnUpdateResourceState,
IconName = "AnimalRabbitOff",
IconVariant = IconVariant.Filled
};
builder.WithCommand(
name: "clear-cache",
displayName: "Clear Cache",
executeCommand: context => OnRunClearCacheCommandAsync(builder, context),
commandOptions: commandOptions);
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;
}
}

前述のコード:

  • AppHost から拡張が見えるように、Aspire.Hosting 名前空間を共有します。
  • IResourceBuilder<RedisResource> に対する拡張メソッドを含む static class です。
  • WithClearCommand という単一の拡張メソッドを定義し、Redis リソースに clear-cache コマンドを登録します。さらに、ダッシュボードがコマンド実行と有効化判定に使う executeCommandupdateState コールバックを定義します。
  • 呼び出し側でチェーン可能にするため、IResourceBuilder<RedisResource> を返します。

WithCommand API は、リソースに適切な注釈を追加し、それらは Aspire ダッシュボード で利用されます。ダッシュボードはこれらの注釈を使って UI にコマンドを描画します。詳細へ進む前に、まず WithCommand メソッドのパラメーターを理解しておきましょう。

  • name: 呼び出すコマンド名。
  • displayName: ダッシュボードに表示するコマンド名。
  • executeCommand: コマンド呼び出し時に実行されるコールバック。C# では型が Func<ExecuteCommandContext, Task<ExecuteCommandResult>>、TypeScript では (context: ExecuteCommandContext) => Promise<ExecuteCommandResult> です。
  • updateState: ダッシュボード上でコマンドを有効化する状態を判定するコールバック。C# では Func<UpdateCommandStateContext, ResourceCommandState> で、CommandOptions.UpdateState 経由で指定します。TypeScript では (context: UpdateCommandStateContext) => Promise<ResourceCommandState> で、CommandOptions 上の updateState フィールドとして指定します。
  • iconName: ダッシュボードに表示するアイコン名。アイコンは省略可能ですが、指定する場合は有効な Fluent UI Blazor アイコン名 である必要があります。
  • iconVariant: ダッシュボードに表示するアイコンのバリアント。有効な値は Regular(既定)または Filled です。

executeCommand コールバックは、コマンド ロジックを実装する場所です。受け取る ExecuteCommandContext から、操作対象リソース、キャンセル、ログなどのコマンド固有データにアクセスできます。

C# ではパラメーター型は Func<ExecuteCommandContext, Task<ExecuteCommandResult>> です。ExecuteCommandContext が公開する内容:

  • ServiceProvider (IServiceProvider) — ロガーや ResourceCommandService などのサービス解決に使用します。
  • ResourceName (string) — コマンド実行対象となるリソース インスタンス名です。
  • CancellationToken (CancellationToken) — コマンド実行のキャンセルに使用します。
  • Logger (ILogger) — ログをリソースのログへ直接書き込むために使用します。これらのログは Aspire ダッシュボード または aspire logs <resource> コマンドで確認できます。

前述の例は、OnRunClearCacheCommandAsync という名前の private static メソッドに処理を委譲してキャッシュをクリアします。

RedisResourceBuilderExtensions.cs
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();
}

前述のコード:

  • Redis リソースから接続文字列を取得します。
  • Redis インスタンスへ接続します。
  • データベース インスタンスを取得します。
  • FLUSHALL コマンドを実行してキャッシュをクリアします。
  • コマンド成功を示す CommandResults.Success() インスタンスを返します。

updateState コールバックは、ダッシュボード上でコマンドを有効化するかどうかを判定します。ダッシュボードはリソース状態が変化するたびにこれを呼び出します。典型的なユース ケースは、破壊的なコマンドを正常なリソースに対してのみ有効化すること、または起動プローブ成功後にのみコマンドを有効化することです。

C# ではパラメーター型は Func<UpdateCommandStateContext, ResourceCommandState> です。UpdateCommandStateContext が公開する内容:

  • ServiceProvider (IServiceProvider) — サービス解決に使用します。
  • ResourceSnapshot (CustomResourceSnapshot) — 正常性ステータス、ライフサイクル状態、プロパティ、URL を含む、リソース インスタンスの不変スナップショットです。

前述の例では、Redis リソースが正常なときだけ clear-cache コマンドを有効化します。

RedisResourceBuilderExtensions.cs
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;
}

前述のコード:

  • サービス プロバイダーからロガー インスタンスを取得します。
  • リソース スナップショットの詳細をログ出力します。
  • リソースが正常なら ResourceCommandState.Enabled、それ以外は ResourceCommandState.Disabled を返します。

カスタム コマンドをテストする

Section titled “カスタム コマンドをテストする”

カスタム コマンドをテストするには、AppHost を更新してリソースとコマンドを配線します。

AppHost.cs
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();

前述のコードは WithClearCommand 拡張メソッドを呼び出し、Redis リソースにカスタム コマンドを追加します。

アプリを実行して Aspire ダッシュボードへ移動します。キャッシュ リソース配下にカスタム コマンドが表示されるはずです。ダッシュボードの Resources ページで、Actions 列の下にある省略記号ボタンを選択します。

Aspire ダッシュボード: カスタム コマンドが表示された Redis キャッシュ リソース。

前述の画像には、Redis リソースへ追加した Clear cache コマンドが表示されています。アイコンは、依存リソースの速度がクリアされることを示すため、取り消し線付きのウサギとして表示されます。

Clear cache コマンドを選択して、Redis リソースのキャッシュをクリアします。コマンドは正常に実行され、キャッシュはクリアされるはずです。コマンドの結果は 通知センター で確認できます。

Aspire ダッシュボード: カスタム コマンドが実行された Redis キャッシュ リソース。

コマンドをプログラムで実行する

Section titled “コマンドをプログラムで実行する”

Aspire ダッシュボード を介してコマンドを実行するだけでなく、ResourceCommandService を使ってプログラムからコマンドを実行することもできます。このサービスは、次のような場合に有用です。

  • アプリケーション コード内からコマンドを実行する
  • ワークフローの一部としてコマンド実行を自動化する
  • リソース制御が必要なカスタム ツールを構築する

最も一般的なシナリオは、他のカスタム コマンド実装からコマンドを呼び出すことです。C# では ExecuteCommandContext.ServiceProvider を使って ResourceCommandService を取得します。TypeScript では、コマンドを登録する前に AppHost の実行コンテキストからサービスを解決し、コールバックでキャプチャします。

カスタム コマンドからコマンドを実行する

Section titled “カスタム コマンドからコマンドを実行する”

次の例は、他のコマンドを実行するカスタム コマンドの作成方法を示しています。このケースでは、“reset-all” コマンドがキャッシュをクリアし、データベースを再起動します。

AppHost.cs
using Aspire.Hosting.ApplicationModel;
using Microsoft.Extensions.DependencyInjection;
var builder = DistributedApplication.CreateBuilder(args);
var cache = builder.AddRedis("cache")
.WithClearCommand(); // 前述のとおり "clear-cache" コマンドを定義
var database = builder.AddPostgres("postgres")
.WithCommand("restart", "Restart Database", async (context, ct) =>
{
// データベース再起動の実装
return CommandResults.Success();
});
var api = builder.AddProject<Projects.Api>("api")
.WithReference(cache)
.WithReference(database)
.WithCommand("reset-all", "Reset Everything", async (context, ct) =>
{
var commandService = context.ServiceProvider.GetRequiredService<ResourceCommandService>();
context.Logger.LogInformation("Starting full system reset...");
var clearResult = await commandService.ExecuteCommandAsync(
resource: cache.Resource,
commandName: "clear-cache",
cancellationToken: ct);
var restartResult = await commandService.ExecuteCommandAsync(
resource: database.Resource,
commandName: "restart",
cancellationToken: ct);
if (!clearResult.Success || !restartResult.Success)
{
return CommandResults.Failure("System reset failed");
}
context.Logger.LogInformation("System reset completed successfully");
return CommandResults.Success();
});
builder.Build().Run();

この例では:

  • C# では context パラメーターでサービス プロバイダーとロガーにアクセスします。
  • TypeScript では、コマンドを登録する前に AppHost 実行コンテキストのサービス プロバイダーから ResourceCommandService を取得します。
  • C# では cache.Resourcedatabase.Resource を使ってリソース インスタンスに対してコマンドを実行し、TypeScript では "cache""postgres" などのリソース名文字列を使って実行します。
  • 各コマンド結果の成功を確認してから次へ進みます。

リソース ID でコマンドを実行する

Section titled “リソース ID でコマンドを実行する”

リソース ID またはリソース名を使ってコマンドを実行することもできます。リソース ID はリソース インスタンスの一意識別子で、リソース名は表示名です(この方法を使うには一意である必要があります)。

AppHost.cs
var result = await commandService.ExecuteCommandAsync(
resourceId: "cache",
commandName: "clear-cache",
cancellationToken: cancellationToken);
if (result.Success)
{
logger.LogInformation("Command executed successfully");
}
else if (result.Canceled)
{
logger.LogWarning("Command was canceled");
}
else
{
logger.LogError("Command failed: {Message}", result.Message);
}

リソース識別子引数(C# の resourceId、または TypeScript で文字列を渡す場合の最初の引数)には、次のいずれかを指定できます:

  • リソースの一意 ID(例: レプリカを持つリソースの cache-abcdwxyz
  • 重複名がない場合の表示名(例: cache

レプリカを持つリソースでコマンドを実行する

Section titled “レプリカを持つリソースでコマンドを実行する”

複数レプリカを持つリソースに対して IResource オーバーロードでコマンドを実行すると、すべてのインスタンスで並列実行されます。

AppHost.cs
// レプリカを持つリソースでは、すべてのインスタンスでコマンドが実行される
var result = await commandService.ExecuteCommandAsync(
resource: cache.Resource,
commandName: "clear-cache",
cancellationToken: cancellationToken);

TypeScript では、前述のリソース識別子オーバーロードを使います。特定のレプリカ ID を渡して 1 つのインスタンスを対象にするか、重複名がない場合は一意のリソース名を渡します。

複数レプリカ上で実行した場合の動作:

  • コマンドはすべてのインスタンスで並列実行されます。
  • すべてのコマンドが成功した場合、結果は成功を示します。
  • いずれかのコマンドが失敗した場合、結果には失敗の詳細が含まれます。
  • 非成功のすべてがキャンセルだった場合、結果はキャンセルを示します。

ExecuteCommandResult 型はコマンドの結果を保持します。両言語で同じ概念フィールドを公開します。

  • Success / success (boolean) — コマンドが正常完了したかどうか。
  • Canceled / canceled (boolean) — ユーザー操作でコマンドがキャンセルされたかどうか。
  • Message / message (string?)通知センター と CLI 出力に表示される人間向けメッセージ。成功メッセージとエラー メッセージの両方に使われます。
  • Data / data (CommandResultData?) — 省略可能な構造化出力(プレーン テキスト、JSON、Markdown)。コマンドから構造化出力を返す を参照してください。

CommandResults ヘルパー クラスは、一般的な形状のファクトリー メソッドを提供します。

// ペイロードなしで成功
return CommandResults.Success();
// ステータス メッセージ付きで失敗
return CommandResults.Failure("Error occurred during execution");
// キャンセル(通常は確認プロンプトでユーザーが戻った場合)
return CommandResults.Canceled();
// 例外由来の失敗(Exception.Message を使用)
return CommandResults.Failure(ex);

コマンドから構造化出力を返す

Section titled “コマンドから構造化出力を返す”

リソース コマンドは、成功/失敗シグナルに加えて、プレーン テキスト、JSON、Markdown の構造化ペイロードを返せます。このペイロードは Aspire パイプライン全体を自動で流れます。

  • Dashboard: 成功通知に View response アクションが追加され、ペイロードを表示するテキスト ビジュアライザー ダイアログが開きます。Json 結果は JSON シンタックス ハイライトに固定され、Markdown 結果は Markdown レンダリングに固定されます。DisplayImmediately を設定すると、コマンド完了直後にダイアログが開きます(クリック不要)。

  • CLI: ステータス メッセージは stderr に、ペイロードは stdout に出力されるため、jq のようなツールへ安全にパイプできます。

    Terminal
    aspire resource cache issue-access-token | jq -r .token
  • MCP tools: ステータス メッセージの後に続く 2 番目の TextContentBlock として、ペイロードがツール応答に追加されます。

一般的なシナリオは、カスタム コマンドから アクセス トークン を発行することです。開発者はそれをダッシュボードから直接コピーしたり、CLI から Bearer ヘッダーにパイプしたりできます。Text モードでは結果はトークン文字列のみ、Json モードでは有効期限とスコープを含む結果になります。

成功結果へペイロードを添付するには、CommandResults.Success(message, result, resultFormat) オーバーロードを使います。message は通知センター/CLI のステータス、result はペイロード文字列、resultFormat はダッシュボード ビジュアライザーの解釈方法を制御します。

C# — AppHost.cs
using System.Security.Cryptography;
using Aspire.Hosting.ApplicationModel;
var builder = DistributedApplication.CreateBuilder(args);
builder.AddProject<Projects.MyService>("myservice")
.WithCommand(
name: "issue-access-token",
displayName: "Issue Access Token",
executeCommand: context =>
{
var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
return Task.FromResult(CommandResults.Success(
message: "Access token issued.",
result: token,
resultFormat: CommandResultFormat.Text));
});
builder.Build().Run();

有効期限とスコープ付き JSON を返すには、形式を CommandResultFormat.Json に切り替えます。

C# — AppHost.cs
using System.Security.Cryptography;
using System.Text.Json;
using Aspire.Hosting.ApplicationModel;
var builder = DistributedApplication.CreateBuilder(args);
builder.AddProject<Projects.MyService>("myservice")
.WithCommand(
name: "issue-access-token",
displayName: "Issue Access Token",
executeCommand: context =>
{
var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32));
var json = JsonSerializer.Serialize(new
{
token,
expiresAt = DateTimeOffset.UtcNow.AddHours(1),
scopes = new[] { "read", "write" },
});
return Task.FromResult(CommandResults.Success(
message: "Access token issued.",
result: json,
resultFormat: CommandResultFormat.Json));
});
builder.Build().Run();

CommandResultFormat には 3 つの値があります。

ダッシュボードでの動作
Textプレーン テキスト。固定形式なしでテキスト ビジュアライザー ダイアログに開き、ユーザーは別の構文へ切り替えて確認できます。
JsonJSON。ビジュアライザーは JSON モードに固定され、シンタックス ハイライトと整形表示を行います。
MarkdownMarkdown。ビジュアライザーは Markdown レンダリングに固定されます。

コマンドに表示すべきペイロードがない場合は、Data を省略してください(CommandResults.Success() または { success: true } を返します)。

対応する CommandResults.Failure(errorMessage, result, resultFormat) オーバーロードを使うと、失敗結果にも同じペイロード形状を添付できます。これは、ユーザー(または下流ツール)が確認できる診断詳細を表面化するのに便利です。

C# — AppHost.cs
return CommandResults.Failure(
errorMessage: "Migration failed.",
result: errorJson,
resultFormat: CommandResultFormat.Json);

ダッシュボードで結果ダイアログを自動表示する

Section titled “ダッシュボードで結果ダイアログを自動表示する”

既定では、ダッシュボードは 通知センター の通知に View response アクションとしてペイロードを表示します。CommandResultDataDisplayImmediately を設定すると、コマンド完了時にビジュアライザーを即座に開けます。短いコマンドで応答自体が目的の場合に便利です。

C# — AppHost.cs
return CommandResults.Success(
message: "Access token issued.",
value: new CommandResultData
{
Value = json,
Format = CommandResultFormat.Json,
DisplayImmediately = true,
});

リソースが複数レプリカを持つ場合、コマンドはすべてのインスタンスで並列実行されます。複数レプリカからペイロードが返された場合、最初に成功したペイロードのみが呼び出し側へ伝搬し、他は破棄されます。

WithCommand で登録した任意のコマンドは、組み込みの startstoprestart コマンドを含め、aspire resource コマンドを使ってターミナルから呼び出せます。

ターミナル
aspire resource <resource-name> <command-name> [--apphost <path>]

CLI はステータス出力とペイロード出力を分離するため、結果はスクリプト処理しやすい形式になります。

  • Message と進捗テキストは stderr に書き込まれます。
  • Data.Value(存在する場合)は stdout にそのまま書き込まれます。JsonText ペイロードはそのまま出力され、Markdown は見出し、リスト、コード ブロックを端末向けに装飾してレンダリングされます。
  • 終了コードは、成功時が 0、失敗またはキャンセル時が非 0 です。

これにより、構造化ペイロードを jq に直接パイプしたり、ファイルへリダイレクトしたり、終了コードで分岐したりできます。

ターミナル — JSON ペイロードを jq にパイプ
$ aspire resource cache issue-access-token | jq -r .token
ey7WqGxk2vL...

ステータス メッセージ Access token issued. は stderr に書き込まれるため、stdout はパイプ用にクリーンなまま保たれます。

ターミナル — スクリプトで終了コードを使用
if aspire resource db migrate > migrations.json; then
echo "Applied $(jq length migrations.json) migrations."
else
echo "Migration failed — see error above."
exit 1
fi