Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue when using Custom Command Creator Pattern #76

Open
jebright opened this issue Jan 16, 2023 · 1 comment
Open

Issue when using Custom Command Creator Pattern #76

jebright opened this issue Jan 16, 2023 · 1 comment

Comments

@jebright
Copy link

When using the custom command pattern, as described here: https://jasperfx.github.io/oakton/guide/bootstrapping.html#custom-command-creators

The functionality as described in the "Improved Run Command" described here: https://jasperfx.github.io/oakton/guide/host/run.html#improved-run-command
does not work. Instead of being able to see your commands or see their usage, you get an error.

For example if I execute this command:

dotnet run -- myconsoleapp

I get this error:

[red]Invalid usage[/]
Unhandled exception. System.Reflection.TargetException: Non-static method requires a target.
at System.Reflection.MethodBase.ValidateInvokeTarget(Object target)
at System.Reflection.RuntimeMethodInfo.InvokeOneParameter(Object obj, BindingFlags invokeAttr, Binder binder, Object parameter, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.PropertyInfo.SetValue(Object obj, Object value)
at Oakton.Parsing.TokenHandlerBase.setValue(Object target, Object value)
at Oakton.Argument.Handle(Object input, Queue1 tokens) at Oakton.Help.UsageGraph.<>c__DisplayClass20_0.<BuildInput>b__0(ITokenHandler h) at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable1 source, Func2 predicate, Boolean& found) at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable1 source, Func2 predicate) at Oakton.Help.UsageGraph.BuildInput(Queue1 tokens, ICommandCreator creator)
at Oakton.CommandFactory.HelpRun(Queue1 queue) at Oakton.CommandFactory.HelpRun(String commandName) at Oakton.CommandFactory.buildRun(Queue1 queue, String commandName)
at Oakton.CommandFactory.BuildRun(IEnumerable`1 args)
at Oakton.CommandExecutor.ExecuteAsync(String[] args)
at Oakton.CommandExecutor.Execute(String[] args)
at Program.

$(String[] args) in C:\Users\Joe\source\MyConsoleApp\Program.cs:line 33

dotnet run -- myconsoleapp ?

In addition if you specify the wrong arguments trying to run your console app, you also get a hard error. This command:

dotnet run --myconsoleapp test123

Gives this error:

[red]Error parsing input[/]System.FormatException: The input string 'test123' was not in a correct format.
at void System.Number.ThrowOverflowOrFormatException(ParsingStatus status, ReadOnlySpan value, TypeCode type)
at int System.Int32.Parse(string s)
at object Oakton.Internal.Conversion.Conversions.<>c__DisplayClass5_0`1.b__0(string x)
at bool Oakton.Argument.Handle(object input, Queue tokens)
at bool Oakton.Help.UsageGraph.<>c__DisplayClass20_0.b__0(ITokenHandler h)
at TSource System.Linq.Enumerable.TryGetFirst(IEnumerable source, Func<TSource, bool> predicate, out bool found)
at TSource System.Linq.Enumerable.FirstOrDefault(IEnumerable source, Func<TSource, bool> predicate)
at object Oakton.Help.UsageGraph.BuildInput(Queue tokens, ICommandCreator creator)
at CommandRun Oakton.CommandFactory.buildRun(Queue queue, string commandName)

Unhandled exception. System.Reflection.TargetException: Non-static method requires a target.
at System.Reflection.MethodBase.ValidateInvokeTarget(Object target)
at System.Reflection.RuntimeMethodInfo.InvokeOneParameter(Object obj, BindingFlags invokeAttr, Binder binder, Object parameter, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.PropertyInfo.SetValue(Object obj, Object value)
at Oakton.Parsing.TokenHandlerBase.setValue(Object target, Object value)
at Oakton.Argument.Handle(Object input, Queue1 tokens) at Oakton.Help.UsageGraph.<>c__DisplayClass20_0.<BuildInput>b__0(ITokenHandler h) at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable1 source, Func2 predicate, Boolean& found) at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable1 source, Func2 predicate) at Oakton.Help.UsageGraph.BuildInput(Queue1 tokens, ICommandCreator creator)
at Oakton.CommandFactory.HelpRun(Queue1 queue) at Oakton.CommandFactory.HelpRun(String commandName) at Oakton.CommandFactory.buildRun(Queue1 queue, String commandName)
at Oakton.CommandFactory.BuildRun(IEnumerable`1 args)
at Oakton.CommandExecutor.ExecuteAsync(String[] args)
at Oakton.CommandExecutor.Execute(String[] args)
at Program.

$(String[] args) in C:\Users\Joe\source\MyConsoleApp\Program.cs:line 33

As long as I specify the proper parameter, Oakton does work. It is just when I try to take advantage of the improved run command to see how the command/inputs are supposed to be structured or if I make a mistake on the input parameters, Oakton seems to crash hard.

I am using version 6.0.0 of Oakton and a .NET 7 console app. My program.cs looks like this:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Oakton;
using Oakton.Commands;
using System.Reflection;

using IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddTransient();
services.AddTransient();
})
//.ConfigureAppConfiguration((hostingContext, config) => { })
.Build();

var executor = CommandExecutor.For(commandFactory =>
{
commandFactory.RegisterCommands(typeof(Program).GetTypeInfo().Assembly);
}
, new DiCommandCreator(host.Services)
);
return executor.Execute(args);

Any my DiCommandCreator class looks like this:

public class DiCommandCreator : ICommandCreator
{
    private readonly IServiceProvider serviceProvider;

    public DiCommandCreator(IServiceProvider serviceProvider)
    {
        this.serviceProvider = serviceProvider;
    }

    public IOaktonCommand CreateCommand(Type commandType)
    {
        return (IOaktonCommand)serviceProvider.GetService(commandType);
    }

    public object CreateModel(Type modelType)
    {
        return serviceProvider.GetService(modelType);
    }
}

I dont think it is anything wrong per se with my command or input (which is just a simple class with one integer property) because if I switch over to not use dependency injection and just use the "RunOaktonCommandsSynchronously" approach, everything works.

return Host.CreateDefaultBuilder(args)
.RunOaktonCommandsSynchronously(args);

Seems like when using the custom command pattern, Oakton just does not work as expected? I need to use IOC in this project and hoping there may be some way to address these issues. Thank you.

@jeremydmiller
Copy link
Member

Hey, sorry, honestly just seeing this. The idiomatic way now is to just spin up the IHost through a NetInput and use the application's main container. Would that help? That's all I use Oakton for at this point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants