uniface.hub

ユニフェイスの開発者ブログ


Title スクリプトも C# で書きたい
  • 2022年5月19日
  • 小西拓実
スクリプトも C# で書きたい

小西です。

自動化スクリプトなんかは PowerShell で書くことが多いのですが、.NET 6 の登場で「もうスクリプトも C# でよくね?」となったので布教のために少し書いてみます。

環境

  • .NET 6+
  • ConsoleAppFramework 4.1.0

プロジェクト作成

dotnet new コマンドを使用してコンソールアプリを作成します。コマンドを実行すると、実行ディレクトリ内に同名の .csproj ファイルと Program.cs ファイルが作成されます。

mkdir my-project
cd my-project
dotnet new console

ConsoleAppFramework の導入

このままでもいいですが、 ConsoleAppFramework を利用すると Generic Host の仕組みをコンソールアプリに適用できて便利なので入れておきます。

dotnet add package ConsoleAppFramework

ConsoleAppFramework を導入することにより、コマンドや引数を受け取るアプリを最小限のコードで実装することができます。

ConsoleApp.Run(args, () => Console.WriteLine("Hello, dotnet!"));

実行

dotnet run コマンドでアプリを実行できます。

dotnet run

複数のコマンド

1つのアプリで複数のコマンドを定義することもできます。

var app = ConsoleApp.Create(args);

app.AddCommand("hello", () =>
{
    Console.WriteLine("Hello, dotnet!");
});

// 引数を受け取るコマンド
app.AddCommand("sleep", async ([Option("t")] int time) =>
{
    await Task.Delay(TimeSpan.FromSeconds(time));
});

app.Run();

dotnet run コマンド実行時の -- 以降の文字列はアプリへの引数として扱われるので、実行したいコマンドと引数を指定して実行します。

dotnet run -- sleep -t 3

DI

コマンド実行時に DI したい場合は ConsoleAppBase から派生したクラスのコンストラクタで注入します。

AddCommand() を使ったデリゲートによる定義時にも DI できるらしいのですが、なぜかうまく注入されませんでした。

using Microsoft.Extensions.Logging;

var app = ConsoleApp.Create(args);

// app.AddCommand("di", (ILogger logger) =>
// {
//    // "ILogger が解決できず、\"Required parameter "logger" not found in argument.\" と怒られる。"
//    // サンプルではコマンドの第一引数に ConsoleAppContext を受け取っているので真似してみたが変わらず。
// });

app.AddCommands<Commands>();    // Commands クラスのパブリックなメソッドがコマンドとして追加される。
app.Run();

public class Commands : ConsoleAppBase
{
    private readonly ILogger<Commands> logger;

    // コンストラクタでの注入
    public Commands(ILogger<Commands> logger)
    {
        this.logger = logger;
    }

    public void Log()
    {
        logger.LogInformation("log log log");
    }
}

自分で作ったクラスを注入する場合は ConsoleAppBuilder を使用して DI を設定したビルダーから ConsoleApp のインスタンスを作成します。

この辺が Generic Host に乗っかってることで統一的な書き方ができてサイコーですね。

using Microsoft.Extensions.DependencyInjection;

var builder = ConsoleApp.CreateBuilder(args);

// DI設定
builder.ConfigureServices(service =>
{
    service.AddSingleton<MyService>();
});

var app = builder.Build();
app.AddCommands<Commands>();
app.Run();

public class Commands : ConsoleAppBase
{
    private readonly MyService service;

    public Commands(MyService service)
    {
        this.service = service;
    }

    public async Task Put()
    {
        await service.Execute();
    }
}

public class MyService
{
    public Task Execute()
    {
        Console.WriteLine("DI!!!");
        return Task.CompletedTask;
    }
}

まとめ

なんだか ConsoleAppFramework についての記事みたいになってしまいましたが、.NET 6 の Minimal API によるクラス定義の排除、ConsoleAppFramework による Generic Host サポートとコマンドのルーティングにより C# のコードをとてもシンプルに記述できるようになったことで、これまでは手軽さから PowerShell で書いていたスクリプトも C# で書くことでデバッグ性や高いパフォーマンスを得られるようになるのではないかと思います。