Så här binder du argument till hanterare i System.CommandLine
Viktigt!
System.CommandLine
är för närvarande i förhandsversion och den här dokumentationen är för version 2.0 beta 4.
Viss information gäller förhandsversionsprodukt som kan ändras avsevärt innan den släpps. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, avseende informationen som visas här.
Processen för att parsa argument och ge dem till kommandohanterarkod kallas parameterbindning. System.CommandLine
har möjlighet att binda många inbyggda argumenttyper. Till exempel kan heltal, uppräkningar och filsystemobjekt som FileInfo och DirectoryInfo vara bundna. Flera System.CommandLine
typer kan också bindas.
Inbyggd argumentverifiering
Argumenten har förväntade typer och aritet. System.CommandLine
avvisar argument som inte matchar dessa förväntningar.
Ett parsfel visas till exempel om argumentet för ett heltalsalternativ inte är ett heltal.
myapp --delay not-an-int
Cannot parse argument 'not-an-int' as System.Int32.
Ett aritetsfel visas om flera argument skickas till ett alternativ som har maximal aritet på ett:
myapp --delay-option 1 --delay-option 2
Option '--delay' expects a single argument but 2 were provided.
Det här beteendet kan åsidosättas genom att ställa in Option.AllowMultipleArgumentsPerToken på true
. I så fall kan du upprepa ett alternativ som har maximal aritet på ett, men endast det sista värdet på raden accepteras. I följande exempel skickas värdet three
till appen.
myapp --item one --item two --item three
Parameterbindning upp till 8 alternativ och argument
I följande exempel visas hur du binder alternativ till kommandohanterarparametrar genom att anropa 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}");
}
Lambda-parametrarna är variabler som representerar värdena för alternativ och argument:
(delayOptionValue, messageOptionValue) =>
{
DisplayIntAndString(delayOptionValue, messageOptionValue);
},
Variablerna som följer lambda representerar alternativet och argumentobjekten som är källorna till alternativet och argumentvärdena:
delayOption, messageOption);
Alternativen och argumenten måste deklareras i samma ordning i lambda och i parametrarna som följer lambda. Om ordningen inte är konsekvent resulterar något av följande scenarier:
- Om out-of-order-alternativen eller argumenten är av olika typer genereras ett körningsfel. En kan till exempel
int
visas där enstring
ska finnas i listan över källor. - Om de oordnade alternativen eller argumenten är av samma typ får hanteraren i tysthet fel värden i de parametrar som anges till den. Alternativet
x
kan till exempelstring
visas därstring
alternativety
ska finnas i listan över källor. I så fall hämtar variabeln för alternativvärdety
alternativvärdetx
.
Det finns överlagringar av SetHandler som stöder upp till 8 parametrar, med både synkrona och asynkrona signaturer.
Parameterbindning fler än 8 alternativ och argument
Om du vill hantera fler än 8 alternativ eller skapa en anpassad typ från flera alternativ kan du använda InvocationContext
eller en anpassad bindemedel.
Använda InvocationContext
En SetHandler överlagring ger åtkomst till InvocationContext objektet och du kan använda InvocationContext
för att hämta valfritt antal alternativ och argumentvärden. Exempel finns i Ange utgångskoder och Hantera avslutning.
Använda en anpassad pärm
Med en anpassad pärm kan du kombinera flera alternativ- eller argumentvärden till en komplex typ och skicka det till en enda hanterarparameter. Anta att du har en Person
typ:
public class Person
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
}
Skapa en klass härledd från BinderBase<T>, där T
är den typ som ska konstrueras baserat på kommandoradsindata:
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)
};
}
Med den anpassade pärmen kan du få din anpassade typ skickad till hanteraren på samma sätt som du får värden för alternativ och argument:
rootCommand.SetHandler((fileOptionValue, person) =>
{
DoRootCommand(fileOptionValue, person);
},
fileOption, new PersonBinder(firstNameOption, lastNameOption));
Här är det fullständiga programmet som föregående exempel hämtas från:
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)
};
}
}
Ange utgångskoder
Det finns Task- returnerar Func-överlagringar av SetHandler. Om hanteraren anropas från asynkron kod kan du returnera en Task<int>
från en hanterare som använder någon av dessa och använda int
värdet för att ange processens slutkod, som i följande exempel:
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);
}
Men om lambda måste vara asynkron kan du inte returnera en Task<int>
. I så fall använder du InvocationContext.ExitCode. Du kan få instansen InvocationContext
inmatad i din lambda med hjälp av en SetHandler-överlagring som anger InvocationContext
som den enda parametern. Med den här SetHandler
överlagringen kan du inte ange IValueDescriptor<T>
objekt, men du kan hämta alternativ- och argumentvärden från parseResult-egenskapen för InvocationContext
, som du ser i följande exempel:
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);
}
Om du inte har asynkront arbete att utföra kan du använda överlagringarna Action . I så fall anger du slutkoden på InvocationContext.ExitCode
samma sätt som med en asynkron lambda.
Slutkoden är som standard 1. Om du inte anger det explicit anges dess värde till 0 när hanteraren avslutas normalt. Om ett undantag utlöses behåller det standardvärdet.
Typer som stöds
I följande exempel visas kod som binder vissa vanliga typer.
Uppräkningar
Värdena för enum
typerna är bundna efter namn och bindningen är skiftlägesokänslig:
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);
Här är exempel på kommandoradsindata och resulterande utdata från föregående exempel:
myapp --color red
myapp --color RED
Red
Red
Matriser och listor
Många vanliga typer som implementeras IEnumerable stöds. Till exempel:
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);
Här är exempel på kommandoradsindata och resulterande utdata från föregående exempel:
--items one --items two --items three
System.Collections.Generic.List`1[System.String]
one
two
three
Eftersom AllowMultipleArgumentsPerToken är inställt true
på resulterar följande indata i samma utdata:
--items one two three
Filsystemtyper
Kommandoradsprogram som fungerar med filsystemet kan använda typerna FileSystemInfo, FileInfooch DirectoryInfo . I följande exempel visas användningen av 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);
Med FileInfo
och DirectoryInfo
mönstermatchningskoden krävs inte:
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);
Andra typer som stöds
Många typer som har en konstruktor som tar en enskild strängparameter kan bindas på det här sättet. Kod som skulle fungera med fungerar till exempel med FileInfo
en Uri i stället.
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);
Förutom filsystemtyperna och Uri
stöds följande typer:
bool
byte
DateTime
DateTimeOffset
decimal
double
float
Guid
int
long
sbyte
short
uint
ulong
ushort
Använda System.CommandLine objekt
Det finns en SetHandler
överlagring som ger dig åtkomst till objektet InvocationContext . Objektet kan sedan användas för att komma åt andra System.CommandLine
objekt. Du har till exempel åtkomst till följande objekt:
InvocationContext
Exempel finns i Ange utgångskoder och Hantera avslutning.
CancellationToken
Information om hur du använder CancellationTokenfinns i Hantera avslutning.
IConsole
IConsole gör testning och många utökningsscenarier enklare än att använda System.Console
. Den är tillgänglig i egenskapen InvocationContext.Console .
ParseResult
Objektet ParseResult är tillgängligt i egenskapen InvocationContext.ParseResult . Det är en singleton-struktur som representerar resultatet av parsning av kommandoradsindata. Du kan använda den för att kontrollera om det finns alternativ eller argument på kommandoraden eller för att hämta ParseResult.UnmatchedTokens egenskapen. Den här egenskapen innehåller en lista över de token som parsades men som inte matchade något konfigurerat kommando, alternativ eller argument.
Listan över omatchade token är användbar i kommandon som fungerar som omslutningar. Ett omslutningskommando tar en uppsättning token och vidarebefordrar dem till ett annat kommando eller en annan app. Kommandot sudo
i Linux är ett exempel. Det tar namnet på en användare att personifiera följt av ett kommando som ska köras. Till exempel:
sudo -u admin apt update
Den här kommandoraden apt update
skulle köra kommandot som användaren admin
.
Om du vill implementera ett omslutningskommando som det här anger du kommandoegenskapen TreatUnmatchedTokensAsErrors till false
. ParseResult.UnmatchedTokens
Sedan innehåller egenskapen alla argument som inte uttryckligen tillhör kommandot. I föregående exempel ParseResult.UnmatchedTokens
skulle innehålla apt
token och update
. Kommandohanteraren kan sedan vidarebefordra UnmatchedTokens
till ett nytt gränssnittsanrop, till exempel.
Anpassad validering och bindning
Om du vill ange anpassad valideringskod anropar AddValidator du kommandot, alternativet eller argumentet enligt följande exempel:
var delayOption = new Option<int>("--delay");
delayOption.AddValidator(result =>
{
if (result.GetValueForOption(delayOption) < 1)
{
result.ErrorMessage = "Must be greater than 0";
}
});
Om du vill parsa och verifiera indata använder du ett ParseArgument<T> ombud, som du ser i följande exempel:
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.
}
});
Föregående kod anges isDefault
till true
så att ombudet parseArgument
anropas även om användaren inte angav något värde för det här alternativet.
Här är några exempel på vad du kan göra med ParseArgument<T>
som du inte kan göra med AddValidator
:
Parsning av anpassade typer, till exempel
Person
klassen i följande exempel: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 };
Parsning av andra typer av indatasträngar (till exempel parsa "1,2,3" i
int[]
).Dynamisk aritet. Du har till exempel två argument som definieras som strängmatriser, och du måste hantera en sekvens med strängar i kommandoradsindata. Med ArgumentResult.OnlyTake metoden kan du dynamiskt dela upp indatasträngarna mellan argumenten.