ASP.NET 2.0 時代(!)に作られた社内Webサービスを自動操作するCLIアプリをMicroBatchFramework + Seleniumで作ったのでその備忘録です。
背景
対象の社内Web サービスはその日作業した作業の製番や、工数を入力するWebサービスです。いかにも業務アプリっぽい見た目のUIで、毎日使う割にはちょっと入力が大変でした。
幸いにもExcelファイルをアップロードして工数情報を一括入力する機能があるので、アプリから自動でExcelファイルをアップロードするCLIアプリを作りました。
言語とライブラリ
- .NET Core 3.0
- MicroBatchFramework
- Selenium
- SeriLog
Webの操作はSeleniumを使用します。自動操作中の画面は表示させたくないのでドライバはChromeDriverを使用します。
※Chromeには画面非表示で自動操作する--headlessモード
があります
MicroBatchFrameworkはC#大統一論でお馴染みの@neueccさんが作成されたCommandLineParserです。Generic Hostな書き心地でCLIが書けるため、C#大統一論信者の私にはぴったりです。
MicroBatchFrameworkのリポジトリはこちらです。
Project の作成
プロジェクトを作って、必要なパッケージを追加します。
dotnet new console -o HogeBot cd HogeBot dotnet add package Microsoft.Extensions.Hosting dotnet add package Microsoft.Extensions.Options.ConfigurationExtensions dotnet add package MicroBatchFramework dotnet add package Selenium.Support dotnet add package Selenium.WebDriver dotnet add package Selenium.WebDriver.ChromeDriver dotnet add package Serilog.Extensions.Logging dotnet add package Serilog.Sinks.Console dotnet add package Serilog.Sinks.File
csporj の変更
下記仕様に沿い.csprojを変更します
- 配布するファイルは少な目にしたい
- サイズは軽くしたい
- 起動は早くしたい
- 対象OSはWindows10 x64に絞る
- appsettings.jsonを同封する
- 発行時にChromeDriverを同封する
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> + <PublishSingleFile>true</PublishSingleFile> + <RuntimeIdentifier>win10-x64</RuntimeIdentifier> + <PublishReadyToRun>true</PublishReadyToRun> + <PublishTrimmed>true</PublishTrimmed> + <WebDriverPlatform>win32</WebDriverPlatform> + <PublishChromeDriver>true</PublishChromeDriver> + <AssemblyVersion>1.0.0.0</AssemblyVersion> + <FileVersion>1.0.0.0</FileVersion> + <Version>1.0.0.0</Version> </PropertyGroup> ... + <ItemGroup> + <None Update="appsettings.json"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + </None> + </ItemGroup> </Project>
ChromeDriverはWebDriverPlatform
とPublishChromeDriver
を指定することで発行時にbinフォルダからpublishフォルダにコピーしてくれるようです。
上記挙動は@jsakamotoさんのnupkg-selenium-webdriver-chromedriverによるもののようです。 頭が上がりませんね。
コーディング
あとはちょいちょいとコーディングしていきます。
Program.cs
appsettings.json
はExeと同じパスに同盟のファイルが存在する場合はそちらのファイルを優先するよう、以下の感じで実装しています。
var currentDir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); //途中省略 if (File.Exists(Path.Combine(currentDir, APP_CONFIG_FILE))) config.SetBasePath(currentDir);
- Logファイル出力先はExeと同じパスに出力したいので、Logのパスの指定は
Program.cs
内で指定します。 Consoleに対するLoggingFilterの設定はappsettings.json
から読み込むようにしています。
using System; using System.Diagnostics; using System.IO; using Serilog; using MicroBatchFramework; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using HogeBot.Models; using HogeBot.Interfaces; using HogeBot.Services; namespace HogeBot { class Program { static readonly string APP_CONFIG_FILE = "appsettings.json"; static readonly string APPLICATION_SETTING_SECTION = "ApplicationSetting"; static readonly string LOG_FILE = "Log.log"; static async System.Threading.Tasks.Task Main(string[] args) { // Exeのあるパスを取得 var currentDir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); var host = BatchHost.CreateDefaultBuilder() .ConfigureHostConfiguration(config => { // Exeと同じ場所にconfigがある場合はそちらを優先する if (File.Exists(Path.Combine(currentDir, APP_CONFIG_FILE))) config.SetBasePath(currentDir); config.AddJsonFile(APP_CONFIG_FILE); }) .ConfigureServices((hostContext, services) => { services.Configure<AppSettingModel>(hostContext.Configuration.GetSection(APPLICATION_SETTING_SECTION)); services.AddTransient<IAutoOperationable, ChromeBrowserAutoOperator>(); }) .ConfigureLogging((hostContext, logging) => { logging.ClearProviders(); logging.AddSerilog(); // Serilogの設定 Log.Logger = new LoggerConfiguration() .Enrich.FromLogContext() .WriteTo.File(Path.Combine(currentDir, LOG_FILE)) .ReadFrom.Configuration(hostContext.Configuration) .CreateLogger(); }); await host.RunBatchEngineAsync<HogeBotBatchService>(args); Log.CloseAndFlush(); } } }
BatchService.cs
using System; using System.Reflection; using HogeBot.Interfaces; using HogeBot.Models; using MicroBatchFramework; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace HogeBot.Services { public class HogeBotBatchService : BatchBase { private readonly AppSettingModel _config; private readonly ILogger<HogeBotBatchService> _logger; private readonly IAutoOperationable _operator; public HogeBotBatchService(IOptions<AppSettingModel> config, ILogger<HogeBotBatchService> logger, IAutoOperationable operator) { this._config = config.Value; this._logger = logger; this._operator = operator; } [Command("version", "Version情報を表示します")] public void ShowVersion() { var version = Assembly.GetExecutingAssembly() .GetCustomAttribute<AssemblyFileVersionAttribute>() .Version; Console.WriteLine($"Version:{version}"); } [Command(nameof(Import), "Excelファイルの工数情報をWebサービスへインポートします。")] public void Import([Option("f", "Excelファイルのパスを指定.")]string filePath) { try { _operator.AutoOperate(_config.URI, filePath); } catch (Exception) { _logger.LogError("Import Failed."); throw; } } } }
おわり
MicroBatchFrameworkはversion 1.5でExitCodeも対応されたこともあって
個人的には小規模なCLIならMicroBatchFramework一択って感じです。
Seleniumの話はまた別の記事で触れたいと思います。