13
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

基本的なコンソールアプリケーションの構造 2021年版

Posted at

かつてはコマンドライン引数の解析も自前でやる必要があったりログ出力も苦労して作ってたりするけど、今時はそういうパッケージがあるのでガンガン利用すべし。

というわけで今時のコンソールアプリを作るための初期構造。

使うパッケージ

  <ItemGroup>
    <PackageReference Include="DryIoc.Microsoft.DependencyInjection" Version="5.1.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
    <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="5.0.0" />
    <PackageReference Include="NLog.Extensions.Logging" Version="1.7.2" />
    <PackageReference Include="System.CommandLine" Version="2.0.0-beta1.21216.1" />
  </ItemGroup>
  • ログ出力 NLog
  • DI DryIoc
  • コマンドライン解析 System.CommandLine

System.CommandLine はまだプレビュー版だけれども自前で解析するよりまし。
GenericHost 使わないのはバッチ処理とかちょっとしたコマンドとかには過剰なので。

コード

program.cs

using DryIoc;
using DryIoc.Microsoft.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using System;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.IO;
using System.Threading.Tasks;

    class Program
    {
        static IServiceProvider serviceProvider;
        static async Task<int> Main(string[] args)
        {
            serviceProvider = BuildServiceProvider(BuildConfiguration());

            var rootCommand = BuildCommand();
            return await rootCommand.InvokeAsync(args);
        }
        static IConfigurationRoot BuildConfiguration()
        {
            return new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile(path: "appsettings.json")
                .Build();
        }

        static IServiceProvider BuildServiceProvider(IConfigurationRoot configuration)
        {
            var services = new ServiceCollection();
            services.Configure<SampleSettings>(configuration.GetSection("SampleSettings"));
            services.AddLogging(loggingBuilder =>
            {
                loggingBuilder.ClearProviders();
                loggingBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
                loggingBuilder.AddNLog();
            });

            var container = new DryIoc.Container(rules => rules.With()).WithDependencyInjectionAdapter(services);
            container.Register<ISampleService, SampleService>(reuse: Reuse.Scoped);
            return container.BuildServiceProvider();
        }
        static RootCommand BuildCommand()
        {
            var rootCommand = new RootCommand();
            rootCommand.Description = "console app sample";
            rootCommand.AddOption(new Option<string>(aliases: new string[] { "--name", "-n" }));
            rootCommand.AddOption(new Option<string>(aliases: new string[] { "--value", "-v" }));

            rootCommand.Handler = CommandHandler.Create<ConsoleAppOptions>(options => { ExecuteCommand(options); });

            return rootCommand;
        }
        static void ExecuteCommand(ConsoleAppOptions options)
        {
            using(var scope = serviceProvider.CreateScope())
            {
                var service = scope.ServiceProvider.GetRequiredService<ISampleService>();
                service.DoSomething(options);
            }
        }
        
    }

Service

// interface
    interface ISampleService
    {
        void DoSomething(ConsoleAppOptions options);
    }
// implementation
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

    public class SampleService : ISampleService
    {
        private readonly SampleSettings sampleSettings;
        private readonly ILogger<SampleService> logger;
        public SampleService(IOptions<SampleSettings> settings, ILogger<SampleService> logger)
        {
            this.sampleSettings = settings.Value;
            this.logger = logger;
        }
        public void DoSomething(ConsoleAppOptions options)
        {
            logger.LogTrace($"execute {nameof(DoSomething)}");
            logger.LogTrace($"options {nameof(options.Name)} {options.Name}");
            logger.LogTrace($"options {nameof(options.Value)} {options.Value}");
            logger.LogTrace($"settings {nameof(sampleSettings.Id)} {sampleSettings.Id}");
            logger.LogTrace($"settings {nameof(sampleSettings.Name)} {sampleSettings.Name}");
        }
    }

コマンドラインオプション

    public class ConsoleAppOptions
    {
        public string Name { get; set; }
        public string Value { get; set; }
    }

設定ファイルマッピングクラス

    public class SampleSettings
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

設定ファイル

{
  "Logging": {
    "LogLevel": {
      "Default": "Error",
      "Microsoft": "Trace"
    }
  },
  "SampleSettings": {
    "Id": 1,
    "Name": "name"
  }
}

json 設定ファイルの読み込みやらDI設定やらコマンドライン解析やら、だいたいこんな感じ。

実行

[ConsoleAppSample] > dotnet run --name 111 --value 222
[TRACE] [1] ConsoleAppSample.SampleService.DoSomething#19 execute DoSomething
[TRACE] [1] ConsoleAppSample.SampleService.DoSomething#20 options Name 111
[TRACE] [1] ConsoleAppSample.SampleService.DoSomething#21 options Value 222
[TRACE] [1] ConsoleAppSample.SampleService.DoSomething#22 settings Id 1
[TRACE] [1] ConsoleAppSample.SampleService.DoSomething#23 settings Name name

設定ファイルの内容とコマンドライン引数はそれぞれのクラスにマッピングされる。

その他

`git log' みたいなコマンドのコマンドとかRootCommandにCommandを追加すればよい。
DIはAutofacでもDryIocでも Unity Container でも何でもよい。最終的には Microsoft.Extensions.DependencyInjection を通して使う。

参考

System.CommandLine
https://docs.microsoft.com/ja-jp/archive/msdn-magazine/2019/march/net-parse-the-command-line-with-system-commandline

DryIoc
https://github.com/dadhi/DryIoc

NLog
https://nlog-project.org/

13
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?