|
using Microsoft.Extensions.DependencyInjection; |
|
|
|
public interface IProgram |
|
{ |
|
public const int DefaultExitCode = 0; |
|
public const int TimeoutExitCode = 124; |
|
|
|
private static TProgram DefaultProgramFactory<TProgram>(IEnumerable<string> _) |
|
where TProgram : IProgram |
|
{ |
|
// Configure Apartment State |
|
if (Enum.TryParse( |
|
Environment.GetEnvironmentVariable("DOTNET_APARTMENT_STATE", EnvironmentVariableTarget.Process), |
|
true, out ApartmentState apartmentState)) |
|
{ |
|
if (!Thread.CurrentThread.TrySetApartmentState(apartmentState)) |
|
{ |
|
Console.Error.WriteLine($"Warning: Cannot set apartment state of main thread. ({apartmentState})"); |
|
} |
|
} |
|
|
|
var program = Activator.CreateInstance<TProgram>(); |
|
return program; |
|
} |
|
|
|
protected static int Start<TProgram>( |
|
Func<IEnumerable<string>, TProgram>? factory = null) |
|
where TProgram : IProgram |
|
=> Start(Environment.GetCommandLineArgs().Skip(1), factory); |
|
|
|
protected static int Start<TProgram>( |
|
IEnumerable<string> args, |
|
Func<IEnumerable<string>, TProgram>? factory = null) |
|
where TProgram : IProgram |
|
{ |
|
// Generate program instance |
|
var program = (factory ?? DefaultProgramFactory<TProgram>).Invoke(args); |
|
|
|
// Configure Services |
|
var services = new ServiceCollection(); |
|
program.OnConfigureServices(args, services); |
|
|
|
// Configure Service Provider Factory |
|
var spOptions = new ServiceProviderOptions() |
|
{ |
|
ValidateOnBuild = true, |
|
ValidateScopes = true, |
|
}; |
|
|
|
var spFactory = new DefaultServiceProviderFactory(spOptions); |
|
|
|
// Configure Service Provider |
|
var provider = spFactory.CreateServiceProvider(services); |
|
program.OnConfigureProvider(args, services, provider); |
|
|
|
// Configure Process Cancellation |
|
var cts = new CancellationTokenSource(); |
|
|
|
Console.CancelKeyPress += (s, e) => |
|
{ |
|
program.OnCancelKeyPress(s, e, args, services, provider); |
|
if (!e.Cancel) cts.Cancel(); |
|
}; |
|
|
|
AppDomain.CurrentDomain.UnhandledException += (s, e) |
|
=> program.OnUnhandledExceptionThrown(s, e, args, services, provider); |
|
|
|
// Run Async Main Method |
|
var task = program.MainAsync(args, services, provider, cts.Token); |
|
|
|
// Retrieve and Setup Exit Code |
|
int exitCode = DefaultExitCode; |
|
if (program.OnAwaitMainThread(task, cts.Token, args, services, provider)) |
|
{ |
|
exitCode = TimeoutExitCode; |
|
program.OnTimeout(args, services, provider); |
|
} |
|
else |
|
exitCode = task.Result; |
|
|
|
program.OnTerminating(exitCode, args, services, provider); |
|
Environment.ExitCode = exitCode; |
|
return exitCode; |
|
} |
|
|
|
Task<int> MainAsync( |
|
IEnumerable<string> args, |
|
IServiceCollection services, |
|
IServiceProvider provider, |
|
CancellationToken cancellationToken); |
|
|
|
void OnConfigureServices( |
|
IEnumerable<string> args, |
|
IServiceCollection services) |
|
{ } |
|
|
|
void OnConfigureProvider( |
|
IEnumerable<string> args, |
|
IServiceCollection services, |
|
IServiceProvider provider) |
|
{ } |
|
|
|
bool OnAwaitMainThread( |
|
Task<int> task, CancellationToken cancellationToken, |
|
IEnumerable<string> args, |
|
IServiceCollection services, |
|
IServiceProvider provider) |
|
=> task.Wait(Timeout.Infinite, cancellationToken); |
|
|
|
void OnCancelKeyPress( |
|
object? sender, ConsoleCancelEventArgs e, |
|
IEnumerable<string> args, |
|
IServiceCollection services, |
|
IServiceProvider provider) |
|
{ } |
|
|
|
void OnUnhandledExceptionThrown( |
|
object? sender, UnhandledExceptionEventArgs e, |
|
IEnumerable<string> args, |
|
IServiceCollection services, |
|
IServiceProvider provider) |
|
{ } |
|
|
|
void OnTimeout(IEnumerable<string> args, |
|
IServiceCollection services, |
|
IServiceProvider provider) |
|
{ } |
|
|
|
void OnTerminating( |
|
int? exitCode, |
|
IEnumerable<string> args, |
|
IServiceCollection services, |
|
IServiceProvider provider) |
|
{ } |
|
} |