System.CommandLine でミドルウェアを使う方法

重要

System.CommandLine は現在プレビュー段階であり、このドキュメントはバージョン 2.0 beta 4 を対象としています。 一部の情報は、リリース前に大きく変更される可能性があるプレリリースされた製品に関するものです。 Microsoft は、ここに記載されている情報について、明示または黙示を問わず、一切保証しません。

この記事では、System.CommandLine ライブラリを使って構築したコマンドライン アプリ内でミドルウェアを使用する方法について説明します。 ミドルウェアの使用は、ほとんどの System.CommandLine ユーザーが考慮する必要のない高度なトピックです。

ミドルウェアの概要

各コマンドには、System.CommandLine で入力に基づいてルーティングを行うハンドラーがありますが、アプリケーション ロジックの呼び出し前に入力を短絡または変更するメカニズムもあります。 解析と呼び出しの間には、カスタマイズ可能な責任のチェーンがあります。 System.CommandLine の多くの組み込み機能がこの機能を使っています。 --help--version のオプションでは、このようにハンドラーへの呼び出しを短絡しています。

パイプラインの呼び出しごとに、ParseResult に基づいてアクションを実行して早期に戻ることができます。または、パイプラインの次の項目を呼び出すことを選択できます。 このフェーズで、ParseResult を置き換えることもできます。 チェーン内の最後の呼び出しは、指定したコマンドのハンドラーです。

ミドルウェア パイプラインに追加する

CommandLineBuilderExtensions.AddMiddleware を呼び出すことで、このパイプラインに呼び出しを追加することができます。 以下は、カスタム ディレクティブを有効にするコードの例です。 rootCommand というルート コマンドを作成した後、通常どおりにコードでオプション、引数、ハンドラーを追加しています。 次に、ミドルウェアを追加します。

var commandLineBuilder = new CommandLineBuilder(rootCommand);

commandLineBuilder.AddMiddleware(async (context, next) =>
{
    if (context.ParseResult.Directives.Contains("just-say-hi"))
    {
        context.Console.WriteLine("Hi!");
    }
    else
    {
        await next(context);
    }
});

commandLineBuilder.UseDefaults();
var parser = commandLineBuilder.Build();
await parser.InvokeAsync(args);

前述のコードでは、解析結果にディレクティブ [just-say-hi] が見つかった場合に、ミドルウェアから "Hi!" が出力されます。 このとき、コマンドの通常のハンドラーは呼び出されません。 呼び出されないのは、ミドルウェアから next デリゲートを呼び出していないためです。

この例では、contextInvocationContext です。つまり、コマンド処理プロセス全体の "ルート" として機能するシングルトン構造体です。 これは System.CommandLine の中でも、機能の面で最も強力な構造体です。 ミドルウェアでの主な用途は 2 つあります。

  • ミドルウェアがそのカスタム ロジックに対して必須とする依存関係を取得できるアクセス権を BindingContextParserConsoleHelpBuilder に付与することです。
  • InvocationResult または ExitCode のプロパティを設定することで、短絡的な方法でコマンド処理を終了できます。 一例として --help オプションがあり、このように実装されています。

必要な using ディレクティブを含む完成したプログラムを次に示します。

using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Parsing;

class Program
{
    static async Task Main(string[] args)
    {
        var delayOption = new Option<int>("--delay");
        var messageOption = new Option<string>("--message");

        var rootCommand = new RootCommand("Middleware example");
        rootCommand.Add(delayOption);
        rootCommand.Add(messageOption);

        rootCommand.SetHandler((delayOptionValue, messageOptionValue) =>
            {
                DoRootCommand(delayOptionValue, messageOptionValue);
            },
            delayOption, messageOption);

        var commandLineBuilder = new CommandLineBuilder(rootCommand);

        commandLineBuilder.AddMiddleware(async (context, next) =>
        {
            if (context.ParseResult.Directives.Contains("just-say-hi"))
            {
                context.Console.WriteLine("Hi!");
            }
            else
            {
                await next(context);
            }
        });

        commandLineBuilder.UseDefaults();
        var parser = commandLineBuilder.Build();
        await parser.InvokeAsync(args);
    }

    public static void DoRootCommand(int delay, string message)
    {
        Console.WriteLine($"--delay = {delay}");
        Console.WriteLine($"--message = {message}");
    }
}

コマンド ラインの例と、前述のコードの出力結果を次に示します。

myapp [just-say-hi] --delay 42 --message "Hello world!"
Hi!

関連項目

System.CommandLine の概要