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 eenstring
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 waarstring
de optiey
zich in de lijst met bronnen moetx
bevinden. In dat geval krijgt de variabele voor de optiewaardey
de optiewaardex
.
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 Uri
worden 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.Console
ervan. 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 isDefault
true
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.