Skip to content

CommandHandler.Create won't map every Property #2253

@christopherGdynia

Description

@christopherGdynia

Hello there,

CommandHandler.Create wont map every Property and I dont get why.

Version

<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />

My Code:

public class Options
{
    public Option<string> Directory { get; }
    public Option<string> Name { get; }
    public Option<string> Method { get; }
    public Option<string> InterfaceDependencies { get; }
    public Option<string> ClassDependencies { get; }
    public Option<string> Role { get; }
    public Option<string> RouteParams { get; }
    public Option<string> QueryParams { get; }
    public Option<string> SwaggerDocPrefix { get; }
    public Option<string> SpecialQuery { get; }
    public Option<string> SpecialQueryUpdate { get; }
    public Option<string> CustomResponse { get; }
    public Option<bool> AddUser { get; }
    public Option<bool> AddValidator { get; }
    public Option<bool> AddDto { get; }
    public Option<bool> Yes { get; }
    public Option<bool> Force { get; }

    public static readonly IReadOnlyList<string> Methods = new List<string>() { "get", "patch", "post", "delete", "put" };

    public Options()
    {
        Directory = new Option<string>(new string[]
        {
            "-d", "--directory"
        }, "Zieldirectory")
        {
            IsRequired = true,
        };

        Name = new Option<string>(new string[]
        {
            "-n", "--name"
        }, "Name")
        {
            IsRequired = true,
        };

        Method = new Option<string>(new string[]
        {
            "-m", "--method"
        }, "HttpMethode");
        Method.SetDefaultValue("get");
        Method.AddValidator(result =>
        {
            string[] allowedValues = new string[] { "get", "patch", "post", "delete", "put" };

            if (!Array.Exists(allowedValues, v => v.Equals(result.GetValueForOption(Method), StringComparison.OrdinalIgnoreCase)))
            {
                Console.WriteLine($"Fehler: Ungültige Wert für Option {string.Join(", ", Method.Aliases)} '{result.GetValueForOption(Method)}'.");
                Console.WriteLine($"Die Option muss eine der folgenden sein: {string.Join(", ", allowedValues)}.");
                Console.WriteLine();
                Environment.Exit(1);
            }
        });

        InterfaceDependencies = new Option<string>(new string[]
        {
            "--interface-dependencies"
        }, "InterfaceDependencies");

        ClassDependencies = new Option<string>(new string[]
        {
            "--class-dependencies"
        }, "ClassDependencies");

        Role = new Option<string>(new string[]
        {
            "--role"
        }, "Role");

        RouteParams = new Option<string>(new string[]
        {
            "--route-params"
        }, "Zieldirectory");

        QueryParams = new Option<string>(new string[]
        {
            "--query-params"
        }, "Query Params");

        SwaggerDocPrefix = new Option<string>(new string[]
        {
            "-s", "--swagger-doc-prefix"
        }, "SwaggerDocPrefix")
        { IsRequired = true };

        SpecialQuery = new Option<string>(new string[]
        {
            "--special-query"
        }, "SpecialQueryReturnType");

        SpecialQueryUpdate = new Option<string>(new string[]
        {
            "--special-query-update"
        }, "SpecialQueryUpdateModel");
        //SpecialQueryUpdate.AddValidator(result =>
        //{
        //    if (!string.IsNullOrEmpty(result.GetValueForOption(SpecialQuery)))
        //    {
        //        Console.WriteLine($"Fehler: Conflicting Option '{string.Join(", ", SpecialQuery.Aliases)}'.");
        //        Console.WriteLine($"Es kann nur eins von '{string.Join(", ", SpecialQuery.Aliases)}' & '{string.Join(", ", SpecialQueryUpdate.Aliases)}'.");
        //        Console.WriteLine();
        //        Environment.Exit(1);
        //    }
        //});
        //SpecialQuery.AddValidator(result =>
        //{
        //    if (!string.IsNullOrEmpty(result.GetValueForOption(SpecialQueryUpdate)))
        //    {
        //        Console.WriteLine($"Fehler: Conflicting Option '{string.Join(", ", SpecialQueryUpdate.Aliases)}'.");
        //        Console.WriteLine($"Es kann nur eins von '{string.Join(", ", SpecialQuery.Aliases)}' & '{string.Join(", ", SpecialQueryUpdate.Aliases)}'.");
        //        Console.WriteLine();
        //        Environment.Exit(1);
        //    }
        //});
        CustomResponse = new Option<string>(new string[]
        {
            "-r", "--response"
        }, "CustomResponse");

        AddUser = new Option<bool>(new string[]
        {
            "-u", "--user"
        }, "AddUser");

        AddValidator = new Option<bool>(new string[]
        {
            "-v", "--validator"
        }, "AddValidator");

        AddDto = new Option<bool>(new string[]
        {
            "--dto"
        }, "AddDto");

        Yes = new Option<bool>(new string[]
        {
            "-y", "--yes", "-j", "--ja"
        }, "Yes");

        Force = new Option<bool>(new string[]
        {
            "-f", "--force"
        }, "Force");
    }
}

internal class Commands
{
    public RootCommand RootCommand { get; set; }
    public Command CreateProcessors { get; set; }
    public Command AddApiEndpoint { get; set; }

    public Commands(Options options)
    {
        RootCommand = new RootCommand("Tool zum Arbeiten mit Apis und der Datenbank");

        CreateProcessors = new Command("create-processors", "Create processors")
        {
            options.Directory,
            options.Force,
        };

        AddApiEndpoint = new Command("add-api-endpoint", "Add API endpoint")
        {
            options.AddDto,
            options.AddUser,
            options.AddValidator,
            options.ClassDependencies,
            options.CustomResponse,
            options.Directory,
            options.Force,
            options.InterfaceDependencies,
            options.SpecialQuery,
            options.SpecialQueryUpdate,
            options.Method,
            options.Name,
            options.QueryParams,
            options.RouteParams,
            options.Role,
            options.SwaggerDocPrefix,
            options.Yes
        };

        AddCommandsToRoot();

    }

    internal void AddCommandsToRoot()
    {
        RootCommand.Add(AddApiEndpoint);
        RootCommand.Add(CreateProcessors);
    }
}


internal class AddApiEndpointOptions
{
    public string Directory { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;
    public string Method { get; set; } = string.Empty;
    public string InterfaceDependencies { get; set; } = string.Empty;
    public string ClassDependencies { get; set; } = string.Empty;
    public string Role { get; set; } = string.Empty;
    public string RouteParams { get; set; } = string.Empty;
    public string QueryParams { get; set; } = string.Empty;
    public string SwaggerDocPrefix { get; set; } = string.Empty;
    public string SpecialQuery { get; set; } = string.Empty;
    public string SpecialQueryUpdate { get; set; } = string.Empty;
    public string CustomResponse { get; set; } = string.Empty;
    public bool AddUser { get; set; }
    public bool AddValidator { get; set; }
    public bool AddDto { get; set; }
    public bool Yes { get; set; }
    public bool Force { get; set; }

    public bool IsSpecialQuery() => !string.IsNullOrEmpty(SpecialQuery);

    public bool IsSpecialQueryUpdate() => !string.IsNullOrEmpty(SpecialQueryUpdate);

    public bool IsSpecialQueryOrSpecialQueryUpdate() => IsSpecialQuery() || IsSpecialQueryUpdate();

    public string EndpointName
    {
        get => string.Concat(
            TextUtils.ToPascalCase(Method),
            TextUtils.ToPascalCase(Name),
            "Endpoint"
        );
    }

    public string CommandOrQuery
    {
        get => Method == "get" || IsSpecialQuery() ? "Query" : "Command";
    }

    public List<(string type, string name, bool isRouteParam)> TransformedParameters
    {
        get
        {
            List<(string type, string name, bool isRouteParam)> list = new();
            foreach (var item in RouteParams.Split(","))
            {
                if (string.IsNullOrEmpty(item)) continue;
                var splittedItem = item.Split(":").ToList();
                var type = splittedItem.Count > 1 ? splittedItem[1] : "long";
                list.Add((type, splittedItem[0], true));
            }
            foreach (var item in QueryParams.Split(","))
            {
                if (string.IsNullOrEmpty(item)) continue;
                var splittedItem = item.Split(":").ToList();
                var type = splittedItem.Count > 1 ? splittedItem[1] : "long";
                list.Add((type, splittedItem[0], false));
            }
            return list;
        }
    }

    public List<(string type, string name)> TransformedDependencies
    {
        get
        {
            List<(string type, string name)> list = new();
            foreach (var item in InterfaceDependencies.Split(","))
            {
                if (string.IsNullOrEmpty(item)) continue;
                list.Add((item, item[1..].Camelize()));
            }
            foreach (var item in ClassDependencies.Split(","))
            {
                if (string.IsNullOrEmpty(item)) continue;
                list.Add((item, item.Camelize()));
            }
            return list;
        }
    }
}

Options options = new();
Commands commands = new(options);

// this works just fine, it only needs two options
//commands.CreateProcessors.SetHandler(CreateProcessorsHandler.Run, options.Directory, options.Force);

// this is the intended use, but is not different then the example below
//commands.AddApiEndpoint.Handler = CommandHandler.Create(AddApiEndpointHandler.Run);

// note using Newtonsoft.Json;
commands.AddApiEndpoint.Handler = CommandHandler.Create<AddApiEndpointOptions>(options =>
{
    Console.WriteLine(JsonConvert.SerializeObject(options));
});

await commands.RootCommand.InvokeAsync(args);

My Input args

add-api-endpoint -d .\Endpoints\Test\ --special-query TestModel -n Test -s Test -r long -u -v --dto --query-params Email:string --route-params Id --interface-dependencies ITestService --class-dependencies TestService -m post --role Test -f -y

Also tried --user , --user true, -u true

Properties which aren't mapped: AddUser, AddDto, AddValidator, CustomResponse

My Output:

{
  "Directory": ".\\Endpoints\\Test\\",
  "Name": "Test",
  "Method": "post",
  "InterfaceDependencies": "ITestService",
  "ClassDependencies": "TestService",
  "Role": "Test",
  "RouteParams": "Id",
  "QueryParams": "Email:string",
  "SwaggerDocPrefix": "Test",
  "SpecialQuery": "TestModel",
  "SpecialQueryUpdate": "",
  "CustomResponse": "",
  "AddUser": false,
  "AddValidator": false,
  "AddDto": false,
  "Yes": true,
  "Force": true,
  "EndpointName": "PostTestEndpoint",
  "CommandOrQuery": "Query",
  "TransformedParameters": [
    { "Item1": "long", "Item2": "Id", "Item3": true },
    { "Item1": "string", "Item2": "Email", "Item3": false }
  ],
  "TransformedDependencies": [
    { "Item1": "ITestService", "Item2": "testService" },
    { "Item1": "TestService", "Item2": "testService" }
  ]
}

I appreciate every help.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions