Skip to content

Commit

Permalink
refactor(DependencyInjectionCommandCreator): use ActivatorUtilities f…
Browse files Browse the repository at this point in the history
…or command creation

refactor(HostedCommandExtensions): simplify Oakton registration and command execution

feat(HostedCommandExtensions): add OaktonOptions for configuration

refactor(HostedCommandExtensions): remove HostedCommandOptions in favor of OaktonOptions

test(HostedCommandsTester): update tests to use OaktonOptions and default command
  • Loading branch information
tgardner committed Sep 22, 2024
1 parent 0134fb9 commit 4648f92
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 40 deletions.
4 changes: 2 additions & 2 deletions src/Oakton/DependencyInjectionCommandCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public DependencyInjectionCommandCreator(IServiceProvider serviceProvider)

public IOaktonCommand CreateCommand(Type commandType)
{
var scope = _serviceProvider.CreateScope();
return (IOaktonCommand)scope.ServiceProvider.GetRequiredService(commandType);
using var scope = _serviceProvider.CreateScope();
return ActivatorUtilities.CreateInstance(scope.ServiceProvider, commandType) as IOaktonCommand;
}

public object CreateModel(Type modelType)
Expand Down
58 changes: 31 additions & 27 deletions src/Oakton/HostedCommandExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#nullable enable

using JasperFx.Core.Reflection;
using JasperFx.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Oakton.Internal;
using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

Expand All @@ -16,29 +19,20 @@ public static class HostedCommandExtensions
/// Register Oakton commands and services with the application's service collection.
/// </summary>
/// <param name="services"></param>
/// <param name="factoryBuilder"></param>
public static void AddOakton(this IServiceCollection services, Action<CommandFactory>? factoryBuilder = null)
public static void AddOakton(this IServiceCollection services, Action<OaktonOptions>? options = null)
{
var registrationFactory = new CommandFactory();
registrationFactory.ApplyFactoryDefaults(factoryBuilder);
registrationFactory.ApplyExtensions(services);

var commands = registrationFactory.AllCommandTypes();
foreach (var commandType in commands)
{
if (commandType.IsConcrete() && commandType.CanBeCastTo<IOaktonCommand>())
{
services.AddScoped(commandType);
}
}
services.Configure(options);

services.TryAddSingleton<ICommandCreator, DependencyInjectionCommandCreator>();

services.TryAddSingleton<ICommandFactory>((ctx) =>
{
var creator = ctx.GetRequiredService<ICommandCreator>();
var oaktonOptions = ctx.GetRequiredService<IOptions<OaktonOptions>>().Value;
var factory = new CommandFactory(creator);
factory.ApplyFactoryDefaults(factoryBuilder);
factory.ApplyFactoryDefaults(Assembly.GetEntryAssembly());
oaktonOptions.Factory?.Invoke(factory);
return factory;
});

Expand All @@ -52,11 +46,10 @@ public static void AddOakton(this IServiceCollection services, Action<CommandFac
/// </summary>
/// <param name="host">An already built IHost</param>
/// <param name="args"></param>
/// <param name="builder">Optionally configure additional command options</param>
/// <returns></returns>
public static int RunHostedOaktonCommands(this IHost host, string[] args, Action<HostedCommandOptions>? builder = null)
public static int RunHostedOaktonCommands(this IHost host, string[] args)
{
return RunHostedOaktonCommandsAsync(host, args, builder).GetAwaiter().GetResult();
return RunHostedOaktonCommandsAsync(host, args).GetAwaiter().GetResult();
}

/// <summary>
Expand All @@ -68,12 +61,10 @@ public static int RunHostedOaktonCommands(this IHost host, string[] args, Action
/// <param name="args"></param>
/// <param name="builder">Optionally configure additional command options</param>
/// <returns></returns>
public static Task<int> RunHostedOaktonCommandsAsync(this IHost host, string[] args, Action<HostedCommandOptions>? builder = null)
public static Task<int> RunHostedOaktonCommandsAsync(this IHost host, string[] args)
{
var options = new HostedCommandOptions();
builder?.Invoke(options);

args = args.ApplyArgumentDefaults(options.OptionsFile);
var options = host.Services.GetRequiredService<IOptions<OaktonOptions>>().Value;
args = ApplyArgumentDefaults(args, options);

var executor = host.Services.GetRequiredService<CommandExecutor>();

Expand All @@ -94,9 +85,22 @@ public static Task<int> RunHostedOaktonCommandsAsync(this IHost host, string[] a
return executor.ExecuteAsync(args);
}

private static void ApplyFactoryDefaults(this CommandFactory factory, Action<CommandFactory>? builder = null)
private static string[] ApplyArgumentDefaults(string[] args, OaktonOptions options)
{
factory.ApplyFactoryDefaults(Assembly.GetEntryAssembly());
builder?.Invoke(factory);
// Workaround for IISExpress / VS2019 erroneously putting crap arguments
args = args.FilterLauncherArgs();

// Gotta apply the options file here before the magic "run" gets in
if (options.OptionsFile.IsNotEmpty())
{
args = CommandExecutor.ReadOptions(options.OptionsFile).Concat(args).ToArray();
}

if (args == null || args.Length == 0 || args[0].StartsWith('-'))
{
args = new[] { options.DefaultCommand }.Concat(args ?? Array.Empty<string>()).ToArray();
}

return args;
}
}
6 changes: 0 additions & 6 deletions src/Oakton/HostedCommandOptions.cs

This file was deleted.

10 changes: 10 additions & 0 deletions src/Oakton/OaktonOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace Oakton;

public class OaktonOptions
{
public string OptionsFile { get; set; }
public Action<CommandFactory> Factory { get; set; }
public string DefaultCommand { get; set; } = "run";
}
23 changes: 18 additions & 5 deletions src/Tests/HostedCommandsTester.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Oakton;
using System;
using Xunit;

namespace Tests;
Expand All @@ -14,15 +15,19 @@ public void CanInjectServicesIntoCommands()
.ConfigureServices(services =>
{
services.AddSingleton<TestDependency>();
services.AddOakton(factory =>
services.AddOakton(options =>
{
factory.RegisterCommand<TestDICommand>();
options.Factory = factory =>
{
factory.RegisterCommand<TestDICommand>();
};
options.DefaultCommand = "TestDI";
});
});

var app = builder.Build();

app.RunHostedOaktonCommands(new string[] { "TestDI" });
app.RunHostedOaktonCommands(Array.Empty<string>());

Assert.Equal(1, TestDICommand.Value);
}
Expand All @@ -33,17 +38,25 @@ public class TestInput

public record TestDependency(int Value = 1);

public class TestDICommand : OaktonCommand<TestInput>
public class TestDICommand : OaktonCommand<TestInput>, IDisposable
{
public static int Value { get; set; } = 0;
private readonly TestDependency _dep;
public TestDICommand(TestDependency dep)
{
Value = dep.Value;
_dep = dep;
}

public override bool Execute(TestInput input)
{
Value = _dep.Value;
return true;
}

public void Dispose()
{
Value = 0;
GC.SuppressFinalize(this);
}
}
}

0 comments on commit 4648f92

Please sign in to comment.