diff --git a/src/Spectre.Console.Extensions.Hosting/Infrastructure/HostEnabledCommandApp.cs b/src/Spectre.Console.Extensions.Hosting/Infrastructure/HostEnabledCommandApp.cs new file mode 100644 index 0000000..fe9a87e --- /dev/null +++ b/src/Spectre.Console.Extensions.Hosting/Infrastructure/HostEnabledCommandApp.cs @@ -0,0 +1,69 @@ +using Microsoft.Extensions.Hosting; +using Spectre.Console.Cli; + +namespace Spectre.Console.Extensions.Hosting.Infrastructure; + +internal class HostEnabledCommandApp : IHostEnabledCommandApp +{ + private readonly TypeRegistrar _typeRegistrar; + private readonly CommandApp _underlyingCommandApp; + + public HostEnabledCommandApp(TypeRegistrar typeRegistrar) + { + _typeRegistrar = typeRegistrar; + _underlyingCommandApp = new CommandApp(typeRegistrar); + } + + public void Configure(Action configuration) + { + _underlyingCommandApp.Configure(configuration); + } + + public int Run(IEnumerable args) + { + return _underlyingCommandApp.Run(args); + } + + public async Task RunAsync(IEnumerable args) + { + return await _underlyingCommandApp.RunAsync(args); + } + + public void SetHost(IHost host) + { + _typeRegistrar.SetHost(host); + } +} + +internal class HostEnabledCommandApp : IHostEnabledCommandApp +where TDefaultCommand: class, ICommand +{ + private readonly TypeRegistrar _typeRegistrar; + private readonly CommandApp _underlyingCommandApp; + + public HostEnabledCommandApp(TypeRegistrar typeRegistrar) + { + _typeRegistrar = typeRegistrar; + _underlyingCommandApp = new CommandApp(typeRegistrar); + } + + public void Configure(Action configuration) + { + _underlyingCommandApp.Configure(configuration); + } + + public int Run(IEnumerable args) + { + return _underlyingCommandApp.Run(args); + } + + public async Task RunAsync(IEnumerable args) + { + return await _underlyingCommandApp.RunAsync(args); + } + + public void SetHost(IHost host) + { + _typeRegistrar.SetHost(host); + } +} diff --git a/src/Spectre.Console.Extensions.Hosting/Infrastructure/IHostEnabledCommandApp.cs b/src/Spectre.Console.Extensions.Hosting/Infrastructure/IHostEnabledCommandApp.cs new file mode 100644 index 0000000..000d078 --- /dev/null +++ b/src/Spectre.Console.Extensions.Hosting/Infrastructure/IHostEnabledCommandApp.cs @@ -0,0 +1,9 @@ +using Microsoft.Extensions.Hosting; +using Spectre.Console.Cli; + +namespace Spectre.Console.Extensions.Hosting.Infrastructure; + +public interface IHostEnabledCommandApp : ICommandApp +{ + void SetHost(IHost host); +} diff --git a/src/Spectre.Console.Extensions.Hosting/Infrastructure/TypeRegistrar.cs b/src/Spectre.Console.Extensions.Hosting/Infrastructure/TypeRegistrar.cs index 2a83d20..85ec12c 100644 --- a/src/Spectre.Console.Extensions.Hosting/Infrastructure/TypeRegistrar.cs +++ b/src/Spectre.Console.Extensions.Hosting/Infrastructure/TypeRegistrar.cs @@ -1,19 +1,49 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Spectre.Console.Cli; namespace Spectre.Console.Extensions.Hosting.Infrastructure; public sealed class TypeRegistrar : ITypeRegistrar { - private readonly IServiceCollection _builder; + private readonly IServiceCollection _builder = new ServiceCollection(); + private IServiceCollection _hostServiceCollection = null!; + private IHost? _host; - public TypeRegistrar(IServiceCollection builder) + public TypeRegistrar(IHostBuilder builder) { - _builder = builder; + builder.ConfigureServices((_, serviceCollection) => + { + _hostServiceCollection = serviceCollection; + }); + } + + public void SetHost(IHost host) + { + _host = host; } public ITypeResolver Build() { + if (_host == null) + { + throw new NotSupportedException("SetHost must be called before the Resolver can be accessed."); + } + + // copy all registrations from the host ServiceCollection to our internal ServiceCollection, + // so we have them all available, but do not modify the host ServiceCollection ourselves. + foreach (var serviceDescriptor in _hostServiceCollection) + { + var type = serviceDescriptor.ServiceType; + if (serviceDescriptor.ImplementationType != null) + { + _builder.AddSingleton(type, serviceDescriptor.ImplementationType); + continue; + } + + _builder.AddSingleton(type, _ => _host.Services.GetService(type)!); + } + return new TypeResolver(_builder.BuildServiceProvider()); } diff --git a/src/Spectre.Console.Extensions.Hosting/SpectreConsoleHostBuilderExtensions.cs b/src/Spectre.Console.Extensions.Hosting/SpectreConsoleHostBuilderExtensions.cs index f54093a..a43fc9f 100644 --- a/src/Spectre.Console.Extensions.Hosting/SpectreConsoleHostBuilderExtensions.cs +++ b/src/Spectre.Console.Extensions.Hosting/SpectreConsoleHostBuilderExtensions.cs @@ -21,15 +21,14 @@ public static class SpectreConsoleHostBuilderExtensions public static IHostBuilder UseSpectreConsole(this IHostBuilder builder, Action configureCommandApp) { builder = builder ?? throw new ArgumentNullException(nameof(builder)); + var command = new HostEnabledCommandApp(new TypeRegistrar(builder)); + command.Configure(configureCommandApp); builder.ConfigureServices((_, collection) => - { - var command = new CommandApp(new TypeRegistrar(collection)); - command.Configure(configureCommandApp); - collection.AddSingleton(command); - collection.AddHostedService(); - } - ); + { + collection.AddSingleton(command); + collection.AddHostedService(); + }); return builder; } @@ -48,18 +47,17 @@ public static IHostBuilder UseSpectreConsole(this IHostBuilder { builder = builder ?? throw new ArgumentNullException(nameof(builder)); - builder.ConfigureServices((_, collection) => - { - var command = new CommandApp(new TypeRegistrar(collection)); - if (configureCommandApp != null) - { - command.Configure(configureCommandApp); - } + var command = new HostEnabledCommandApp(new TypeRegistrar(builder)); + if (configureCommandApp != null) + { + command.Configure(configureCommandApp); + } - collection.AddSingleton(command); - collection.AddHostedService(); - } - ); + builder.ConfigureServices((_, collection) => + { + collection.AddSingleton(command); + collection.AddHostedService(); + }); return builder; } diff --git a/src/Spectre.Console.Extensions.Hosting/Worker/SpectreConsoleWorker.cs b/src/Spectre.Console.Extensions.Hosting/Worker/SpectreConsoleWorker.cs index 4bb01f4..fc9ddac 100644 --- a/src/Spectre.Console.Extensions.Hosting/Worker/SpectreConsoleWorker.cs +++ b/src/Spectre.Console.Extensions.Hosting/Worker/SpectreConsoleWorker.cs @@ -1,22 +1,27 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Spectre.Console.Cli; +using Spectre.Console.Extensions.Hosting.Infrastructure; namespace Spectre.Console.Extensions.Hosting.Worker; public class SpectreConsoleWorker : IHostedService { - private readonly ICommandApp _commandApp; + private readonly IHostEnabledCommandApp _commandApp; private readonly IHostApplicationLifetime _hostLifetime; + private readonly IHost _host; private readonly ILogger _logger; private int _exitCode; - public SpectreConsoleWorker(ILogger logger, ICommandApp commandApp, - IHostApplicationLifetime hostLifetime) + public SpectreConsoleWorker( + ILogger logger, + IHostEnabledCommandApp commandApp, + IHostApplicationLifetime hostLifetime, + IHost host) { _logger = logger; _commandApp = commandApp; _hostLifetime = hostLifetime; + _host = host; } public Task StartAsync(CancellationToken cancellationToken) @@ -26,6 +31,7 @@ public Task StartAsync(CancellationToken cancellationToken) try { var args = GetArgs(); + _commandApp.SetHost(_host); await Task.Delay(100, cancellationToken); //Just to let Microsoft.Hosting finish. _exitCode = await _commandApp.RunAsync(args); }