Skip to content

Commit f8dbb31

Browse files
committed
the separation of parsing and invocation
1 parent 9a82823 commit f8dbb31

File tree

7 files changed

+180
-101
lines changed

7 files changed

+180
-101
lines changed

docs/standard/commandline/get-started-tutorial.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ But what happens if you ask it to display the help by providing `--help`? Nothin
127127

128128
## Parse the arguments and invoke the ParseResult
129129

130-
System.CommandLine allows the users to specify an action that is invoked when given symbol (command, option, directive or even argument) is parsed successfully. The action is a delegate that takes a <xref:System.CommandLine.ParseResult> parameter and returns an `int` exit code. The exit code is returned by the `ParseResult.Invoke` method and can be used to indicate whether the command was executed successfully or not.
130+
System.CommandLine allows the users to specify an action that is invoked when given symbol (command, directive or option) is parsed successfully. The action is a delegate that takes a <xref:System.CommandLine.ParseResult> parameter and returns an `int` exit code (async actions are also [available](parse-and-invoke.md#asynchronous-actions)). The exit code is returned by the <xref:System.CommandLine.Parsing.ParseResult.Invoke> method and can be used to indicate whether the command was executed successfully or not.
131131

132132
1. Replace the contents of *Program.cs* with the following code:
133133

docs/standard/commandline/handle-termination.md

Lines changed: 0 additions & 36 deletions
This file was deleted.

docs/standard/commandline/index.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ To get started with System.CommandLine, see the following resources:
4444

4545
To learn more, see the following resources:
4646

47+
* [How to parse and invoke the result](parse-and-invoke.md)
4748
* [How to bind arguments to handlers](model-binding.md)
48-
* [How to configure dependency injection](dependency-injection.md)
4949
* [How to configure help in System.CommandLine](help.md)
5050
* [How to enable and customize tab completion](tab-completion.md)
51-
* [How to handle termination](handle-termination.md)
51+
* [How to configure dependency injection](dependency-injection.md)
52+
* [Command-line design guidance](design-guidance.md)
5253
* [System.CommandLine API reference](xref:System.CommandLine)

docs/standard/commandline/model-binding.md

Lines changed: 14 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,47 +15,33 @@ ms.topic: how-to
1515

1616
The process of parsing arguments and providing them to command action code is called *parameter binding*. `System.CommandLine` has the ability to parse many argument types built in. For example, integers, enums, and file system objects such as <xref:System.IO.FileInfo> and <xref:System.IO.DirectoryInfo> can be parsed and exposed to the command action via `ParseResult` type.
1717

18-
## Built-in argument validation
19-
20-
Arguments have expected types and [arity](syntax.md#argument-arity). `System.CommandLine` rejects arguments that don't match these expectations.
21-
22-
For example, a parse error is displayed if the argument for an integer option isn't an integer.
18+
## Explicit parameter binding
2319

24-
```console
25-
myapp --delay not-an-int
26-
```
20+
Before we bind options and arguments to command actions, we need to define a command. The following example shows how to define a command with options and arguments:
2721

28-
```output
29-
Cannot parse argument 'not-an-int' as System.Int32.
30-
```
22+
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="intandstring" :::
3123

32-
An arity error is displayed if multiple arguments are passed to an option that has maximum arity of one:
24+
Now we can bind options to command action, by calling `SetAction` method with a delegate that takes `ParseResult` as an argument and uses <xref:System.CommandLine.ParseResult.GetValue%2A> to obtain parsed values:
3325

34-
```console
35-
myapp --delay-option 1 --delay-option 2
36-
```
26+
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="lambda" :::
3727

38-
```output
39-
Option '--delay' expects a single argument but 2 were provided.
40-
```
28+
After the action is defined, we can parse the command line input and invoke the action:
4129

42-
This behavior can be overridden by setting <xref:System.CommandLine.Option.AllowMultipleArgumentsPerToken?displayProperty=nameWithType> to `true`. In that case you can repeat an option that has maximum arity of one, but only the last value on the line is accepted. In the following example, the value `three` would be passed to the app.
30+
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="invoke" :::
4331

44-
```console
45-
myapp --item one --item two --item three
46-
```
32+
### Getting values by name
4733

48-
## Explicit parameter binding
34+
You can also get values by name, but this requires you to specify the type of the value you want to get.
4935

50-
The following example shows how to bind options to command action, by calling <xref:System.CommandLine.ParseResult.GetValue%2A>:
36+
The following example uses C# collection initializers to create a root command:
5137

52-
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="intandstring" highlight="13-16" :::
38+
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="collectioninitializersyntax" :::
5339

54-
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="intandstringaction" :::
40+
And then it uses the `GetValue` method to get the values by name:
5541

56-
The lambda parameter is just the `ParseResult` are option and argument values are obtained via `GetValue` method call:
42+
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="lambdanames" :::
5743

58-
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="lambda" :::
44+
This overload of `GetValue` gets the parsed or default value for the specified symbol name, in the context of parsed command (not entire symbol tree). It accepts the symbol name, not an [alias](syntax.md#aliases).
5945

6046
### Complex types
6147

@@ -73,14 +59,6 @@ With the custom parser, you can get a custom type the same way you get primitive
7359

7460
:::code language="csharp" source="snippets/model-binding/csharp/ParseArgument.cs" id="personoption" :::
7561

76-
## Set exit codes
77-
78-
There are `void` and <xref:System.Threading.Tasks.Task>-returning [Func](xref:System.Func%601) overloads of <xref:System.CommandLine.Command.SetAction%2A>. If your action is called from async code, you can return an `int` or a [`Task<int>`](xref:System.Threading.Tasks.Task%601) from the delegate that uses one of these, and just return the exit code, as in the following example:
79-
80-
:::code language="csharp" source="snippets/model-binding/csharp/ReturnExitCode.cs" id="returnexitcode" :::
81-
82-
The exit code defaults to 1. If you don't set it explicitly, its value is set to 0 when your action exits normally. If an exception is thrown, it keeps the default value.
83-
8462
## Supported types
8563

8664
The following examples show code that binds some commonly used types.
@@ -156,28 +134,10 @@ Besides the file system types and `string`, the following types are supported:
156134
* `DateOnly`and `TimeOnly`
157135
* `Guid`
158136

159-
### `CancellationToken`
160-
161-
For information about how to use <xref:System.Threading.CancellationToken>, see [How to handle termination](handle-termination.md).
162-
163137
### `CommandLineConfiguration`
164138

165139
<xref:System.CommandLine.CommandLineConfiguration> makes testing as well as many extensibility scenarios easier than using `System.Console`. It exposes two `TextWriter` properties: `Output` and `Error`. They can be set to any `TextWriter` instance, such as a `StringWriter`, which can be used to capture output for testing. An instance of `CommandLineConfiguration` can be obtained from `ParseResult`.
166140

167-
### `ParseResult`
168-
169-
The <xref:System.CommandLine.Parsing.ParseResult> object is available in the command action. It's a structure that represents the results of parsing the command line input. You can use it to check for the presence of options or arguments on the command line or to get the <xref:System.CommandLine.Parsing.ParseResult.UnmatchedTokens?displayProperty=nameWithType> property. This property contains a list of the [tokens](syntax.md#tokens) that were parsed but didn't match any configured command, option, or argument.
170-
171-
The list of unmatched tokens is useful in commands that behave like wrappers. A wrapper command takes a set of [tokens](syntax.md#tokens) and forwards them to another command or app. The `sudo` command in Linux is an example. It takes the name of a user to impersonate followed by a command to run. For example:
172-
173-
```console
174-
sudo -u admin apt update
175-
```
176-
177-
This command line would run the `apt update` command as the user `admin`.
178-
179-
To implement a wrapper command like this one, set the command property <xref:System.CommandLine.Command.TreatUnmatchedTokensAsErrors> to `false`. Then the `ParseResult.UnmatchedTokens` property will contain all of the arguments that don't explicitly belong to the command. In the preceding example, `ParseResult.UnmatchedTokens` would contain the `apt` and `update` tokens. Your command action could then forward the `UnmatchedTokens` to a new shell invocation, for example.
180-
181141
## Custom validation and parsing
182142

183143
To provide custom validation code, call <xref:System.CommandLine.Option.Validators.Add%2A> on your command, option, or argument, as shown in the following example:
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
title: "How to parse and invoke the result"
3+
description: "Learn how to get parsed values and define actions for your commands."
4+
ms.date: 04/07/2022
5+
no-loc: [System.CommandLine]
6+
helpviewer_keywords:
7+
- "command line interface"
8+
- "command line"
9+
- "System.CommandLine"
10+
---
11+
12+
# Parsing and invocation
13+
14+
[!INCLUDE [scl-preview](../../../includes/scl-preview.md)]
15+
16+
System.CommandLine provides a clear separation of command line parsing and action invocation. The parsing process is responsible for parsing command-line input and creating a <xref:System.CommandLine.ParseResult> object that contains the parsed values (and parse errors). The action invocation process is responsible for invoking the action associated with the parsed command, option, or directive (arguments can't have actions).
17+
18+
In the following example from our [Get started with System.CommandLine](get-started-tutorial.md) tutorial, the `ParseResult` is created by parsing the command-line input. No actions are being defined or invoked:
19+
20+
:::code language="csharp" source="snippets/get-started-tutorial/csharp/Stage0/Program.cs" id="all" :::
21+
22+
An action is invoked when a given command (or directive, or option) is parsed successfully. The action is a delegate that takes a <xref:System.CommandLine.ParseResult> parameter and returns an `int` exit code (async actions are also [available](#async)). The exit code is returned by the <xref:System.CommandLine.Parsing.ParseResult.Invoke> method and can be used to indicate whether the command was executed successfully or not.
23+
24+
In the following example from our [Get started with System.CommandLine](get-started-tutorial.md) tutorial, the action is defined for the root command and invoked after parsing the command-line input:
25+
26+
:::code language="csharp" source="snippets/get-started-tutorial/csharp/Stage1/Program.cs" id="all" :::
27+
28+
Some of the built-in symbols, such as <xref:System.CommandLine.Help.HelpOption>, <xref:System.CommandLine.VersionOption> or <xref:System.CommandLine.Completions.SuggestDirective> come with predefined actions. These symbols are automatically added to the root command when you create it, and when you invoke the <xref:System.CommandLine.Parsing.ParseResult>, they "just work". So using actions allows you to focus on your app logic, while the library takes care of parsing and invoking actions for built-in symbols. If you don't like it, you can just stick to the parsing process and not define any actions (like in the first example above).
29+
30+
## ParseResult
31+
32+
The <xref:System.CommandLine.Parsing.ParseResult> type is a class that represents the results of parsing the command line input. You need to use it to get the parsed values for options, and arguments (no matter if you are using actions or not). You can also check if there were any parse errors or unmatched [tokens](syntax.md#tokens).
33+
34+
### GetValue
35+
36+
The <xref:System.CommandLine.Parsing.ParseResult.GetValue%2A> method allows you to retrieve the values of options and arguments:
37+
38+
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="getvalue" :::
39+
40+
You can also get values by name, but this requires you to specify the type of the value you want to get:
41+
42+
:::code language="csharp" source="snippets/model-binding/csharp/Program.cs" id="getvaluebyname" :::
43+
44+
### Parse errors
45+
46+
The <xref:System.CommandLine.Parsing.ParseResult.Errors?displayProperty=nameWithType> property contains a list of parse errors that occurred during the parsing process. Each error is represented by an <xref:System.CommandLine.Parsing.ParseError> object, which contains information about the error, such as the error message and the token that caused the error.
47+
48+
When you call the <xref:System.CommandLine.Parsing.ParseResult.Invoke%2A> method, it returns an exit code that indicates whether the parsing was successful or not. If there were any parse errors, the exit code is non-zero, and all the parse errors get printed to the standard error.
49+
50+
If you are not invoking the <xref:System.CommandLine.Parsing.ParseResult.Invoke%2A> method, you need to handle the errors on your own. For example by printing them:
51+
52+
:::code language="csharp" source="snippets/get-started-tutorial/csharp/Stage0/Program.cs" id="errors" :::
53+
54+
### Unmatched tokens
55+
56+
The <xref:System.CommandLine.Parsing.ParseResult.UnmatchedTokens?displayProperty=nameWithType> property contains a list of the tokens that were parsed but didn't match any configured command, option, or argument.
57+
58+
The list of unmatched tokens is useful in commands that behave like wrappers. A wrapper command takes a set of [tokens](syntax.md#tokens) and forwards them to another command or app. The `sudo` command in Linux is an example. It takes the name of a user to impersonate followed by a command to run. For example:
59+
60+
```console
61+
sudo -u admin apt update
62+
```
63+
64+
This command line would run the `apt update` command as the user `admin`.
65+
66+
To implement a wrapper command like this one, set the command property <xref:System.CommandLine.Command.TreatUnmatchedTokensAsErrors> to `false`. Then the <xref:System.CommandLine.Parsing.ParseResult.UnmatchedTokens?displayProperty=nameWithType> property will contain all of the arguments that don't explicitly belong to the command. In the preceding example, `ParseResult.UnmatchedTokens` would contain the `apt` and `update` tokens.
67+
68+
## Actions
69+
70+
Actions are delegates that are invoked when a command (or an option or a directive) is parsed successfully. They take a <xref:System.CommandLine.ParseResult> parameter and return an `int` (or `Task<int>`) exit code. The exit code is used to indicate whether the action was executed successfully or not.
71+
72+
System.CommandLine provides an abstract base class <xref:System.CommandLine.CommandLineAction> and two derived classes: <xref:System.CommandLine.SynchronousCommandLineAction> and <xref:System.CommandLine.AsynchronousCommandLineAction>. The former is used for synchronous actions that return an `int` exit code, while the latter is used for asynchronous actions that return a `Task<int>` exit code.
73+
74+
You don't need to create a derived type to define an action. You can use the <xref:System.CommandLine.Command.SetAction%2A> method to set an action for a command. The synchronous action can be a delegate that takes a <xref:System.CommandLine.ParseResult> parameter and returns an `int` exit code. The asynchronous action can be a delegate that takes a <xref:System.CommandLine.ParseResult> and <xref:System.Threading.CancellationToken> parameters and returns a `Task<int>`.
75+
76+
:::code language="csharp" source="snippets/get-started-tutorial/csharp/Stage1/Program.cs" id="lambda" :::
77+
78+
### Asynchronous actions
79+
80+
Synchronous and asynchronous actions should not be mixed in the same application. If you want to use asynchronous actions, your application needs to be asynchronous from the top to the bottom. This means that all actions should be asynchronous, and you should use the <xref:System.CommandLine.Command.SetAction%2A> method that accepts a delegate returning a `Task<int>` exit code. Moreover, the <xref:System.Threading.CancellationToken> that is passed to the action delegate needs to be passed further to all the methods that can be canceled, such as file I/O operations or network requests.
81+
82+
On top of that, you need to ensure that the <xref:System.CommandLine.Parsing.ParseResult.InvokeAsync%2A> method is used instead of <xref:System.CommandLine.Parsing.ParseResult.Invoke>. This method is asynchronous and returns a `Task<int>` exit code. It also accepts an optional <xref:System.Threading.CancellationToken> parameter that can be used to cancel the action.
83+
84+
The preceding code uses a `SetAction` overload that gets a [ParseResult](#parseresult) and a <xref:System.Threading.CancellationToken> rather than just `ParseResult`:
85+
86+
:::code language="csharp" source="snippets/handle-termination/csharp/Program.cs" id="asyncaction" :::
87+
88+
#### Process Termination Timeout
89+
90+
<xref:System.CommandLine.CommandLineConfiguration.ProcessTerminationTimeout?displayProperty=nameWithType> enables signaling and handling of process termination (<kbd>Ctrl</kbd>+<kbd>C</kbd>, `SIGINT`, `SIGTERM`) via a <xref:System.Threading.CancellationToken> that is passed to every async action during invocation. It's enabled by default (2 seconds), but you can set it to `null` to disable it.
91+
92+
When enabled, if the action doesn't complete within the specified timeout, the process will be terminated. This is useful for handling the termination gracefully, for example, by saving the state before the process is terminated.
93+
94+
To test the sample code from previous paragraph, run the command with a URL that will take a moment to load, and before it finishes loading, press <kbd>Ctrl</kbd>+<kbd>C</kbd>. On macOS press <kbd>Command</kbd>+<kbd>Period(.)</kbd>. For example:
95+
96+
```dotnetcli
97+
testapp --url https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis
98+
```
99+
100+
```output
101+
The operation was aborted
102+
```
103+
104+
### Exit codes
105+
106+
The exit code is an integer value returned by an action indicating its success or failure. By convention, an exit code of `0` signifies success, while any non-zero value indicates an error. It’s important to define meaningful exit codes in your application to communicate the status of command execution clearly.
107+
108+
Every `SetAction` method has an overload that accepts a delegate returning an `int` exit code where the exit code needs to be provided in explicit way and an overload that returns `0`.
109+
110+
:::code language="csharp" source="snippets/model-binding/csharp/ReturnExitCode.cs" id="returnexitcode" :::

0 commit comments

Comments
 (0)