【C#】汎用ホストを使ってDIと構成管理を備えたコンソールアプリを作る

目次

概要

ASP.NET Coreで標準化されている「汎用ホスト(Generic Host)」の仕組みをコンソールアプリケーションに適用し、依存性の注入(DI)、構成ファイル(appsettings.json)の読み込み、ロギング機能を統合管理する実装パターンです。 これにより、コンソールアプリであってもテスト容易性が高く、保守しやすい疎結合なアーキテクチャを実現できます。

仕様(入出力)

  • 入力
    • 起動引数(args)
    • 構成ファイル(appsettings.jsonなど)
  • 出力
    • ホスト経由で起動されたサービスクラスによる処理実行
    • コンソールおよび設定されたプロバイダへのログ出力
  • 動作フロー
    1. ホストビルダーによる初期化(DIコンテナ生成)。
    2. IHostedService を実装したメインサービスの起動。
    3. ビジネスロジックの実行。
    4. 処理完了後のアプリケーション停止(シャットダウン)。

基本の使い方

NuGetパッケージ Microsoft.Extensions.Hosting をインストールし、Program.cs でホストを構築します。

dotnet add package Microsoft.Extensions.Hosting

コード全文

機能ごとにクラスを分割した構成です。エントリーポイント、設定クラス、ホスト管理サービス、ビジネスロジックの4つに分かれています。

1. Program.cs (エントリーポイント)

ホストの構築と実行を行います。

using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;

namespace BatchApplication
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // ホストを構築し、実行を開始する
            // RunConsoleAsyncを使うとCtrl+Cでのキャンセルにも対応しやすい
            await CreateHostBuilder(args).Build().RunAsync();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostContext, services) =>
                {
                    // Startupクラスへサービス登録処理を委譲
                    new Startup(hostContext.Configuration).ConfigureServices(services);
                });
    }
}

2. Startup.cs (DI設定)

サービスの登録処理を集約します。

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace BatchApplication
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            // 実際の処理を行うクラスをDIコンテナに登録
            services.AddTransient<IBatchProcessor, SampleBatchProcessor>();

            // ホストのライフサイクルで実行されるメインサービス
            services.AddHostedService<BatchExecutorService>();
        }
    }
}

3. BatchExecutorService.cs (実行管理)

アプリの起動と終了を制御するホストサービスです。

using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace BatchApplication
{
    public class BatchExecutorService : IHostedService
    {
        private readonly IHostApplicationLifetime _appLifetime;
        private readonly IBatchProcessor _processor;
        private readonly ILogger<BatchExecutorService> _logger;

        // コンストラクタインジェクションで依存関係を受け取る
        public BatchExecutorService(
            IHostApplicationLifetime appLifetime,
            IBatchProcessor processor,
            ILogger<BatchExecutorService> logger)
        {
            _appLifetime = appLifetime;
            _processor = processor;
            _logger = logger;
        }

        // アプリ起動時に呼ばれるメソッド
        public async Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("バッチ処理を開始します...");

            try
            {
                // ビジネスロジックの実行
                await _processor.ExecuteAsync();
            }
            finally
            {
                // 重要: 処理が終わったらアプリケーションを明示的に終了させる
                _appLifetime.StopApplication();
            }
        }

        // アプリ終了時に呼ばれるメソッド
        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("バッチ処理を終了します。");
            return Task.CompletedTask;
        }
    }
}

4. SampleBatchProcessor.cs (ビジネスロジック)

実際の処理内容を記述するクラスです。

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace BatchApplication
{
    // インターフェース定義
    public interface IBatchProcessor
    {
        Task ExecuteAsync();
    }

    // 実装クラス
    public class SampleBatchProcessor : IBatchProcessor
    {
        private readonly ILogger<SampleBatchProcessor> _logger;

        public SampleBatchProcessor(ILogger<SampleBatchProcessor> logger)
        {
            _logger = logger;
        }

        public Task ExecuteAsync()
        {
            // ここに実際の業務ロジックを記述
            _logger.LogInformation("メイン処理を実行中...");
            
            // 擬似的な処理待機
            Console.WriteLine("データの変換処理を行っています...");
            
            return Task.CompletedTask;
        }
    }
}

カスタムポイント

  • 設定ファイルの読み込み
    • Host.CreateDefaultBuilder はデフォルトで appsettings.jsonappsettings.{Environment}.json、環境変数を読み込みます。設定値が必要な場合は IConfiguration をインジェクトして取得してください。
  • ログ出力先の変更
    • 標準ではコンソールログが出力されます。ファイルへの出力などが必要な場合は、SerilogNLog などのライブラリを追加し、ConfigureLogging で設定を行います。

注意点

  1. StopApplicationの重要性
    • コンソールアプリ(バッチ)の場合、処理が終わってもホストプロセスは自動的には終了しません。必ず IHostApplicationLifetime.StopApplication() を呼び出して、アプリを正常終了させる必要があります。
  2. 非同期の実装
    • すべてのメソッドチェーンは Task ベースの非同期処理となります。ブロッキングなコード(Task.Wait().Result)を書くとデッドロックの原因になるため、必ず await を使用してください。

応用

環境変数の利用

環境変数 DOTNET_ENVIRONMENTDevelopmentProduction に設定することで、読み込む設定ファイルを自動的に切り替えることができます。

// 実行時の環境変数設定例 (PowerShell)
// $env:DOTNET_ENVIRONMENT = "Development"
// dotnet run

まとめ

汎用ホストを導入することで、コンソールアプリの構造が「起動処理」「構成設定」「ビジネスロジック」に明確に分離されます。これによりコードの見通しが良くなるだけでなく、単体テストの導入や機能拡張が容易になり、長期的にメンテナンス可能なアプリケーションを構築できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次