System.CommandLine で依存関係の挿入を構成する方法

重要

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

カスタム バインダーを使って、カスタム型をコマンド ハンドラーに挿入します。

次の理由から、ハンドラー固有の依存関係の挿入 (DI) をお勧めします。

  • 多くの場合、コマンドライン アプリは短時間のプロセスであり、起動時コストがパフォーマンスに大きな影響を及ぼす可能性があります。 タブ補完を計算する必要がある場合は、特にパフォーマンスの最適化が重要です。 コマンドライン アプリは、比較的長時間のプロセスになる傾向がある Web アプリや GUI アプリとは異なります。 短時間のプロセスに対して不要な起動時間をかけることは適切ではありません。
  • 複数のサブコマンドを持つコマンドライン アプリを実行すると、それらのサブコマンドのうち 1 つだけが実行されます。 実行されないサブコマンドのためにアプリに依存関係を構成すると、パフォーマンスが不必要に低下します。

DI を構成するには、BinderBase<T> から派生するクラスを作成します。この T はインスタンスを挿入するインターフェイスです。 GetBoundValue メソッドのオーバーライドで、挿入するインスタンスを取得して返します。 次の例では、ILogger の既定のロガー実装を挿入しています。

public class MyCustomBinder : BinderBase<ILogger>
{
    protected override ILogger GetBoundValue(
        BindingContext bindingContext) => GetLogger(bindingContext);

    ILogger GetLogger(BindingContext bindingContext)
    {
        ILoggerFactory loggerFactory = LoggerFactory.Create(
            builder => builder.AddConsole());
        ILogger logger = loggerFactory.CreateLogger("LoggerCategory");
        return logger;
    }
}

SetHandler メソッドを呼び出すときに、挿入したクラスのインスタンスをラムダに渡し、バインダー クラスのインスタンスをサービスの一覧で渡します。

rootCommand.SetHandler(async (fileOptionValue, logger) =>
    {
        await DoRootCommand(fileOptionValue!, logger);
    },
    fileOption, new MyCustomBinder());

次のコードは、前述の例を含む完成したプログラムです。

using System.CommandLine;
using System.CommandLine.Binding;
using Microsoft.Extensions.Logging;

class Program
{
    static async Task Main(string[] args)
    {
        var fileOption = new Option<FileInfo?>(
              name: "--file",
              description: "An option whose argument is parsed as a FileInfo");

        var rootCommand = new RootCommand("Dependency Injection sample");
        rootCommand.Add(fileOption);

        rootCommand.SetHandler(async (fileOptionValue, logger) =>
            {
                await DoRootCommand(fileOptionValue!, logger);
            },
            fileOption, new MyCustomBinder());

        await rootCommand.InvokeAsync("--file scl.runtimeconfig.json");
    }

    public static async Task DoRootCommand(FileInfo aFile, ILogger logger)
    {
        Console.WriteLine($"File = {aFile?.FullName}");
        logger.LogCritical("Test message");
        await Task.Delay(1000);
    }

    public class MyCustomBinder : BinderBase<ILogger>
    {
        protected override ILogger GetBoundValue(
            BindingContext bindingContext) => GetLogger(bindingContext);

        ILogger GetLogger(BindingContext bindingContext)
        {
            ILoggerFactory loggerFactory = LoggerFactory.Create(
                builder => builder.AddConsole());
            ILogger logger = loggerFactory.CreateLogger("LoggerCategory");
            return logger;
        }
    }
}

こちらもご覧ください

の概要