using System; using System.IO; using System.Threading.Tasks; using Autofac; using Autofac.Extensions.DependencyInjection; using MfGames.ToolBuilder.Globals; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Serilog; namespace MfGames.ToolBuilder { /// /// A builder pattern for creating the tool. This wraps much of the hosting /// infrastructure with some opinionated decisions and reduces the amount of /// boilerplate needed to configure the tool. /// public class ToolBuilder { private readonly string[] arguments; private readonly ConfigToolGlobalService configService; private readonly IHostBuilder hostBuilder; private readonly LoggingToolGlobalService loggingService; public ToolBuilder( string applicationName, string internalName, string[] arguments) { // Create our various services. this.arguments = arguments; this.ApplicationName = applicationName; this.InternalName = internalName; this.configService = new ConfigToolGlobalService() .WithInternalName(this.InternalName); this.loggingService = new LoggingToolGlobalService(); // Set up logging first so we can report the loading process. This // sets up the Serilog.Log.Logger which means we can use that for // everything beyond this point. this.loggingService.Configure(arguments); // Start up the basic configuration. // // Normally, we would use Host.CreateDefaultBuilder(args) to create // this, but when run as a stand-alone application in someone's // $HOME on Linux, this causes an inotify watcher to be registered // on the entire directory tree (which takes a really long time). // // We also don't need most of the default features. this.hostBuilder = new HostBuilder() .UseDefaultServiceProvider( (context, options) => { bool isDevelopment = context.HostingEnvironment.IsDevelopment(); options.ValidateScopes = isDevelopment; options.ValidateOnBuild = isDevelopment; }) .UseConsoleLifetime() .ConfigureAppConfiguration(this.ConfigureAppConfiguration) .UseSerilog() .UseServiceProviderFactory( new AutofacServiceProviderFactory()) .ConfigureServices(this.ConfigureServices) .ConfigureContainer( this.ConfigureContainer); } /// /// Gets the human-readable name of the application. /// public string ApplicationName { get; } /// /// Gets the internal name of the application. /// public string InternalName { get; } public static ToolBuilder Create( string applicationName, string internalName, string[] arguments) { return new ToolBuilder(applicationName, internalName, arguments); } public ToolBuilder ConfigureContainer( Action configure) { this.hostBuilder.ConfigureContainer(configure); return this; } public ToolBuilder ConfigureServices( Action configure) { this.hostBuilder.ConfigureServices(configure); return this; } /// /// Finishes building the tool, parses the arguments, and runs the /// command. /// /// An error code, 0 for successful, otherwise false. public async Task RunAsync() { try { await this.hostBuilder .RunConsoleAsync() .ConfigureAwait(false); } catch (Exception exception) { Log.Fatal( exception, "There was a problem running the command: {Arguments}", this.arguments); return Environment.ExitCode == 0 ? 1 : Environment.ExitCode; } // Get the exit code and return it. return Environment.ExitCode; } private void ConfigureAppConfiguration( HostBuilderContext context, IConfigurationBuilder builder) { builder.SetBasePath(Directory.GetCurrentDirectory()); this.configService.Configure(builder, this.arguments); } private void ConfigureContainer( HostBuilderContext context, ContainerBuilder builder) { // We want to get logging up and running as soon as possible. We // also hook up the logging to the process exit in an attempt to // make sure the logger is properly flushed before exiting. builder.RegisterInstance(Log.Logger).As().SingleInstance(); AppDomain.CurrentDomain.ProcessExit += (_, _) => Log.CloseAndFlush(); // Register the global services as singletons. builder .RegisterInstance(this.configService) .AsSelf() .SingleInstance(); builder .RegisterInstance(this.loggingService) .AsSelf() .SingleInstance(); // Register the components required to make the CLI work. builder.RegisterModule(); } private void ConfigureServices( HostBuilderContext context, IServiceCollection services) { services.AddAutofac(); services.AddHostedService(); } } }