Share via


Argumenten binden aan handlers in System.CommandLine

Belangrijk

System.CommandLine bevindt zich momenteel in PREVIEW en deze documentatie is voor versie 2.0 beta 4. Sommige informatie heeft betrekking op voorlopige versie van het product dat aanzienlijk kan worden gewijzigd voordat het wordt vrijgegeven. Microsoft biedt geen enkele expliciete of impliciete garanties met betrekking tot de informatie die hier wordt verstrekt.

Het proces van het parseren van argumenten en het verstrekken ervan aan opdrachthandlercode wordt parameterbinding genoemd. System.CommandLine heeft de mogelijkheid om veel argumenttypen te binden die zijn ingebouwd. Bijvoorbeeld gehele getallen, opsommingen en bestandssysteemobjecten, zoals FileInfo en DirectoryInfo kunnen worden gebonden. Er kunnen ook verschillende System.CommandLine typen worden gebonden.

Validatie van ingebouwd argument

Argumenten hebben verwachte typen en arity. System.CommandLine hiermee worden argumenten geweigerd die niet overeenkomen met deze verwachtingen.

Er wordt bijvoorbeeld een parseringsfout weergegeven als het argument voor een optie voor een geheel getal geen geheel getal is.

myapp --delay not-an-int
Cannot parse argument 'not-an-int' as System.Int32.

Er wordt een arity-fout weergegeven als meerdere argumenten worden doorgegeven aan een optie met een maximale arity van één:

myapp --delay-option 1 --delay-option 2
Option '--delay' expects a single argument but 2 were provided.

Dit gedrag kan worden overschreven door in te stellen Option.AllowMultipleArgumentsPerToken op true. In dat geval kunt u een optie herhalen met een maximale arity van één, maar alleen de laatste waarde op de regel wordt geaccepteerd. In het volgende voorbeeld wordt de waarde three doorgegeven aan de app.

myapp --item one --item two --item three

Parameterbinding tot 8 opties en argumenten

In het volgende voorbeeld ziet u hoe u opties koppelt aan opdrachthandlerparameters door aan te roepen SetHandler:

var delayOption = new Option<int>
    ("--delay", "An option whose argument is parsed as an int.");
var messageOption = new Option<string>
    ("--message", "An option whose argument is parsed as a string.");

var rootCommand = new RootCommand("Parameter binding example");
rootCommand.Add(delayOption);
rootCommand.Add(messageOption);

rootCommand.SetHandler(
    (delayOptionValue, messageOptionValue) =>
    {
        DisplayIntAndString(delayOptionValue, messageOptionValue);
    },
    delayOption, messageOption);

await rootCommand.InvokeAsync(args);
public static void DisplayIntAndString(int delayOptionValue, string messageOptionValue)
{
    Console.WriteLine($"--delay = {delayOptionValue}");
    Console.WriteLine($"--message = {messageOptionValue}");
}

De lambda-parameters zijn variabelen die de waarden van opties en argumenten vertegenwoordigen:

(delayOptionValue, messageOptionValue) =>
{
    DisplayIntAndString(delayOptionValue, messageOptionValue);
},

De variabelen die volgen op de lambda vertegenwoordigen de optie- en argumentobjecten die de bronnen van de optie- en argumentwaarden zijn:

delayOption, messageOption);

De opties en argumenten moeten worden gedeclareerd in dezelfde volgorde in de lambda en in de parameters die volgen op de lambda. Als de volgorde niet consistent is, resulteert een van de volgende scenario's:

  • Als de opties of argumenten buiten de volgorde van verschillende typen zijn, wordt er een runtime-uitzondering gegenereerd. Er kan bijvoorbeeld een int worden weergegeven waar een string moet staan in de lijst met bronnen.
  • Als de out-of-orderopties of argumenten van hetzelfde type zijn, krijgt de handler op de achtergrond de verkeerde waarden in de parameters die eraan zijn opgegeven. De optie kan bijvoorbeeld string worden weergegeven waar string de optie y zich in de lijst met bronnen moet x bevinden. In dat geval krijgt de variabele voor de optiewaarde y de optiewaarde x .

Er zijn overbelastingen van SetHandler die ondersteuning voor maximaal 8 parameters, met zowel synchrone als asynchrone handtekeningen.

Parameterbinding meer dan 8 opties en argumenten

Als u meer dan 8 opties wilt afhandelen of een aangepast type wilt maken op basis van meerdere opties, kunt u een aangepaste binder gebruiken InvocationContext of een aangepaste binder gebruiken.

InvocationContext gebruiken

Een SetHandler overbelasting biedt toegang tot het InvocationContext object en kunt u gebruiken InvocationContext om een willekeurig aantal optie- en argumentwaarden op te halen. Zie Afsluitcodes instellen en beëindiging afhandelen voor voorbeelden.

Een aangepaste binder gebruiken

Met een aangepaste binder kunt u meerdere optie- of argumentwaarden combineren in een complex type en deze doorgeven aan één handlerparameter. Stel dat u een Person type hebt:

public class Person
{
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
}

Maak een klasse die is afgeleid van BinderBase<T>, waarbij T het type is dat moet worden samengesteld op basis van opdrachtregelinvoer:

public class PersonBinder : BinderBase<Person>
{
    private readonly Option<string> _firstNameOption;
    private readonly Option<string> _lastNameOption;

    public PersonBinder(Option<string> firstNameOption, Option<string> lastNameOption)
    {
        _firstNameOption = firstNameOption;
        _lastNameOption = lastNameOption;
    }

    protected override Person GetBoundValue(BindingContext bindingContext) =>
        new Person
        {
            FirstName = bindingContext.ParseResult.GetValueForOption(_firstNameOption),
            LastName = bindingContext.ParseResult.GetValueForOption(_lastNameOption)
        };
}

Met de aangepaste binder kunt u uw aangepaste type op dezelfde manier doorgeven aan uw handler als u waarden voor opties en argumenten krijgt:

rootCommand.SetHandler((fileOptionValue, person) =>
    {
        DoRootCommand(fileOptionValue, person);
    },
    fileOption, new PersonBinder(firstNameOption, lastNameOption));

Hier volgt het volledige programma waaruit de voorgaande voorbeelden afkomstig zijn:

using System.CommandLine;
using System.CommandLine.Binding;

public class Program
{
    internal static async Task Main(string[] args)
    {
        var fileOption = new Option<FileInfo?>(
              name: "--file",
              description: "An option whose argument is parsed as a FileInfo",
              getDefaultValue: () => new FileInfo("scl.runtimeconfig.json"));

        var firstNameOption = new Option<string>(
              name: "--first-name",
              description: "Person.FirstName");

        var lastNameOption = new Option<string>(
              name: "--last-name",
              description: "Person.LastName");

        var rootCommand = new RootCommand();
        rootCommand.Add(fileOption);
        rootCommand.Add(firstNameOption);
        rootCommand.Add(lastNameOption);

        rootCommand.SetHandler((fileOptionValue, person) =>
            {
                DoRootCommand(fileOptionValue, person);
            },
            fileOption, new PersonBinder(firstNameOption, lastNameOption));

        await rootCommand.InvokeAsync(args);
    }

    public static void DoRootCommand(FileInfo? aFile, Person aPerson)
    {
        Console.WriteLine($"File = {aFile?.FullName}");
        Console.WriteLine($"Person = {aPerson?.FirstName} {aPerson?.LastName}");
    }

    public class Person
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
    }

    public class PersonBinder : BinderBase<Person>
    {
        private readonly Option<string> _firstNameOption;
        private readonly Option<string> _lastNameOption;

        public PersonBinder(Option<string> firstNameOption, Option<string> lastNameOption)
        {
            _firstNameOption = firstNameOption;
            _lastNameOption = lastNameOption;
        }

        protected override Person GetBoundValue(BindingContext bindingContext) =>
            new Person
            {
                FirstName = bindingContext.ParseResult.GetValueForOption(_firstNameOption),
                LastName = bindingContext.ParseResult.GetValueForOption(_lastNameOption)
            };
    }
}

Afsluitcodes instellen

Er zijn Task-terugkerende Func-overbelastingen van SetHandler. Als uw handler wordt aangeroepen vanuit asynchrone code, kunt u een Task<int> handler retourneren die een van deze gebruikt en de int waarde gebruiken om de afsluitcode van het proces in te stellen, zoals in het volgende voorbeeld:

static async Task<int> Main(string[] args)
{
    var delayOption = new Option<int>("--delay");
    var messageOption = new Option<string>("--message");

    var rootCommand = new RootCommand("Parameter binding example");
    rootCommand.Add(delayOption);
    rootCommand.Add(messageOption);

    rootCommand.SetHandler((delayOptionValue, messageOptionValue) =>
        {
            Console.WriteLine($"--delay = {delayOptionValue}");
            Console.WriteLine($"--message = {messageOptionValue}");
            return Task.FromResult(100);
        },
        delayOption, messageOption);

    return await rootCommand.InvokeAsync(args);
}

Als de lambda zelf echter asynchroon moet zijn, kunt u geen Task<int>. Gebruik in dat geval InvocationContext.ExitCode. U kunt het InvocationContext exemplaar dat is geïnjecteerd in uw lambda ophalen met behulp van een SetHandler-overbelasting die de InvocationContext enige parameter aangeeft. Met deze SetHandler overbelasting kunt u geen objecten opgeven IValueDescriptor<T> , maar u kunt optie- en argumentwaarden ophalen uit de eigenschap ParseResult van InvocationContext, zoals wordt weergegeven in het volgende voorbeeld:

static async Task<int> Main(string[] args)
{
    var delayOption = new Option<int>("--delay");
    var messageOption = new Option<string>("--message");

    var rootCommand = new RootCommand("Parameter binding example");
    rootCommand.Add(delayOption);
    rootCommand.Add(messageOption);

    rootCommand.SetHandler(async (context) =>
        {
            int delayOptionValue = context.ParseResult.GetValueForOption(delayOption);
            string? messageOptionValue = context.ParseResult.GetValueForOption(messageOption);
        
            Console.WriteLine($"--delay = {delayOptionValue}");
            await Task.Delay(delayOptionValue);
            Console.WriteLine($"--message = {messageOptionValue}");
            context.ExitCode = 100;
        });

    return await rootCommand.InvokeAsync(args);
}

Als u geen asynchroon werk hoeft te doen, kunt u de Action overbelastingen gebruiken. In dat geval stelt u de afsluitcode in op InvocationContext.ExitCode dezelfde manier als met een asynchrone lambda.

De afsluitcode is standaard ingesteld op 1. Als u deze niet expliciet instelt, wordt de waarde ervan ingesteld op 0 wanneer de handler normaal wordt afgesloten. Als er een uitzondering wordt gegenereerd, blijft de standaardwaarde behouden.

Ondersteunde typen

In de volgende voorbeelden ziet u code waarmee enkele veelgebruikte typen worden gekoppeld.

Enums

De waarden van typen zijn afhankelijk van enum de naam en de binding is niet hoofdlettergevoelig:

var colorOption = new Option<ConsoleColor>("--color");

var rootCommand = new RootCommand("Enum binding example");
rootCommand.Add(colorOption);

rootCommand.SetHandler((colorOptionValue) =>
    { Console.WriteLine(colorOptionValue); },
    colorOption);

await rootCommand.InvokeAsync(args);

Hier volgt voorbeeld van opdrachtregelinvoer en resulterende uitvoer uit het vorige voorbeeld:

myapp --color red
myapp --color RED
Red
Red

Matrices en lijsten

Veel algemene typen die worden geïmplementeerd IEnumerable , worden ondersteund. Voorbeeld:

var itemsOption = new Option<IEnumerable<string>>("--items")
    { AllowMultipleArgumentsPerToken = true };

var command = new RootCommand("IEnumerable binding example");
command.Add(itemsOption);

command.SetHandler((items) =>
    {
        Console.WriteLine(items.GetType());

        foreach (string item in items)
        {
            Console.WriteLine(item);
        }
    },
    itemsOption);

await command.InvokeAsync(args);

Hier volgt voorbeeld van opdrachtregelinvoer en resulterende uitvoer uit het vorige voorbeeld:

--items one --items two --items three
System.Collections.Generic.List`1[System.String]
one
two
three

Omdat AllowMultipleArgumentsPerToken deze is ingesteld op true, resulteert de volgende invoer in dezelfde uitvoer:

--items one two three

Bestandstypen

Opdrachtregeltoepassingen die met het bestandssysteem werken, kunnen de FileSystemInfo, FileInfoen DirectoryInfo typen gebruiken. In het volgende voorbeeld ziet u het gebruik van FileSystemInfo:

var fileOrDirectoryOption = new Option<FileSystemInfo>("--file-or-directory");

var command = new RootCommand();
command.Add(fileOrDirectoryOption);

command.SetHandler((fileSystemInfo) =>
    {
        switch (fileSystemInfo)
        {
            case FileInfo file                    :
                Console.WriteLine($"File name: {file.FullName}");
                break;
            case DirectoryInfo directory:
                Console.WriteLine($"Directory name: {directory.FullName}");
                break;
            default:
                Console.WriteLine("Not a valid file or directory name.");
                break;
        }
    },
    fileOrDirectoryOption);

await command.InvokeAsync(args);

Bij FileInfo en DirectoryInfo de patroonkoppelingscode is niet vereist:

var fileOption = new Option<FileInfo>("--file");

var command = new RootCommand();
command.Add(fileOption);

command.SetHandler((file) =>
    {
        if (file is not null)
        {
            Console.WriteLine($"File name: {file?.FullName}");
        }
        else
        {
            Console.WriteLine("Not a valid file name.");
        }
    },
    fileOption);

await command.InvokeAsync(args);

Andere ondersteunde typen

Veel typen die een constructor hebben die één tekenreeksparameter gebruikt, kunnen op deze manier worden gebonden. Code waarmee bijvoorbeeld wordt gewerkt, werkt FileInfo in plaats daarvan met een Uri .

var endpointOption = new Option<Uri>("--endpoint");

var command = new RootCommand();
command.Add(endpointOption);

command.SetHandler((uri) =>
    {
        Console.WriteLine($"URL: {uri?.ToString()}");
    },
    endpointOption);

await command.InvokeAsync(args);

Naast de bestandstypen en Uriworden de volgende typen ondersteund:

  • bool
  • byte
  • DateTime
  • DateTimeOffset
  • decimal
  • double
  • float
  • Guid
  • int
  • long
  • sbyte
  • short
  • uint
  • ulong
  • ushort

Objecten gebruiken System.CommandLine

Er is een SetHandler overbelasting die u toegang geeft tot het InvocationContext object. Dat object kan vervolgens worden gebruikt voor toegang tot andere System.CommandLine objecten. U hebt bijvoorbeeld toegang tot de volgende objecten:

InvocationContext

Zie Afsluitcodes instellen en beëindiging afhandelen voor voorbeelden.

CancellationToken

Zie Beëindiging afhandelen voor meer informatie over het gebruik.CancellationToken

IConsole

IConsole maakt testen en veel uitbreidbaarheidsscenario's eenvoudiger dan het gebruik System.Consoleervan. Deze is beschikbaar in de InvocationContext.Console accommodatie.

ParseResult

Het ParseResult object is beschikbaar in de InvocationContext.ParseResult eigenschap. Het is een singleton-structuur die de resultaten vertegenwoordigt van het parseren van de opdrachtregelinvoer. U kunt deze gebruiken om te controleren op de aanwezigheid van opties of argumenten op de opdrachtregel of om de ParseResult.UnmatchedTokens eigenschap op te halen. Deze eigenschap bevat een lijst met de tokens die zijn geparseerd, maar die niet overeenkomen met een geconfigureerde opdracht, optie of argument.

De lijst met niet-overeenkomende tokens is handig in opdrachten die zich gedragen als wrappers. Een wrapper-opdracht gebruikt een set tokens en stuurt deze door naar een andere opdracht of app. De sudo opdracht in Linux is een voorbeeld. De naam van een gebruiker moet worden geïmiteerd, gevolgd door een opdracht die moet worden uitgevoerd. Voorbeeld:

sudo -u admin apt update

Met deze opdrachtregel wordt de apt update opdracht uitgevoerd als de gebruiker admin.

Als u een wrapper-opdracht zoals deze wilt implementeren, stelt u de opdrachteigenschap TreatUnmatchedTokensAsErrors in op false. Vervolgens bevat de ParseResult.UnmatchedTokens eigenschap alle argumenten die niet expliciet deel uitmaken van de opdracht. In het voorgaande voorbeeld ParseResult.UnmatchedTokens zouden de apt en update tokens worden opgenomen. De opdrachthandler kan de opdrachthandler vervolgens doorsturen UnmatchedTokens naar een nieuwe shell-aanroep, bijvoorbeeld.

Aangepaste validatie en binding

Als u aangepaste validatiecode wilt opgeven, roept AddValidator u de opdracht, optie of het argument aan, zoals wordt weergegeven in het volgende voorbeeld:

var delayOption = new Option<int>("--delay");
delayOption.AddValidator(result =>
{
    if (result.GetValueForOption(delayOption) < 1)
    {
        result.ErrorMessage = "Must be greater than 0";
    }
});

Als u de invoer wilt parseren en valideren, gebruikt u een ParseArgument<T> gemachtigde, zoals wordt weergegeven in het volgende voorbeeld:

var delayOption = new Option<int>(
      name: "--delay",
      description: "An option whose argument is parsed as an int.",
      isDefault: true,
      parseArgument: result =>
      {
          if (!result.Tokens.Any())
          {
              return 42;
          }

          if (int.TryParse(result.Tokens.Single().Value, out var delay))
          {
              if (delay < 1)
              {
                  result.ErrorMessage = "Must be greater than 0";
              }
              return delay;
          }
          else
          {
              result.ErrorMessage = "Not an int.";
              return 0; // Ignored.
          }
      });

De voorgaande code wordt zodanig ingesteld isDefaulttrue dat de parseArgument gemachtigde wordt aangeroepen, zelfs als de gebruiker geen waarde voor deze optie heeft ingevoerd.

Hier volgen enkele voorbeelden van wat u hiermee ParseArgument<T> kunt doen AddValidator:

  • Het parseren van aangepaste typen, zoals de Person klasse in het volgende voorbeeld:

    public class Person
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
    }
    
    var personOption = new Option<Person?>(
          name: "--person",
          description: "An option whose argument is parsed as a Person",
          parseArgument: result =>
          {
              if (result.Tokens.Count != 2)
              {
                  result.ErrorMessage = "--person requires two arguments";
                  return null;
              }
              return new Person
              {
                  FirstName = result.Tokens.First().Value,
                  LastName = result.Tokens.Last().Value
              };
          })
    {
        Arity = ArgumentArity.OneOrMore,
        AllowMultipleArgumentsPerToken = true
    };
    
  • Parseren van andere soorten invoertekenreeksen (bijvoorbeeld '1,2,3' parseren in int[]).

  • Dynamische arity. U hebt bijvoorbeeld twee argumenten die zijn gedefinieerd als tekenreeksmatrices en u moet een reeks tekenreeksen afhandelen in de opdrachtregelinvoer. Met ArgumentResult.OnlyTake de methode kunt u de invoerreeksen dynamisch verdelen tussen de argumenten.

Zie ook

System.CommandLine Overzicht