await without blocking the polling loop, and resumes on the matching update — local variables (nameMsg) survive the suspension as normal async state. The builder chain (TextMatches, With{FilterName}, Where, timeouts) is covered in Inline Awaits.
Choosing between inline awaiting and state machines
Parsing what the user sent
The awaited update arrives as a rawMessage or CallbackQuery. Two bundled helpers parse its payload with exactly the same rules the generated router uses for [Arg] parameters and [Pattern] templates — so inline flows and routed handlers never disagree about what “parseable” means.
Command arguments: CommandHelper.ParseArgs
CommandHelper.ParseArgs tokenizes the message text and converts each token with the same engine behind [Arg]: numbers parse invariant-culture, enums parse case-insensitively (names or numeric values), everything else goes through TypeDescriptor converters. Combine it with CommandHelper.MatchCommand when the reply itself carries a command:
ArgSpec mirrors one [Arg] parameter: Type, a name used in error messages, and IsOptional (missing optional tokens yield the type’s default). A failing parse throws CommandArgsParseException, so a try/catch is the natural “re-ask the user” loop. For plain free-text replies without a command prefix, pass argsStartIndex: 0 to treat the whole text as arguments.
Callback data: CallbackPatternMatcher.Match<T…>
For inline keyboards, CallbackPatternMatcher exposes template overloads that are the imperative twin of [Pattern]: literal segments plus {name} placeholders, with captures bound positionally to generic type arguments (up to five, plus a literal-only overload for exact routes):
string binds verbatim, enums parse case-insensitively, numerics parse invariant-culture, other types need a static TryParse. A {*name} wildcard tail binds to a string and captures everything after the separator. Template syntax details, the 64-byte callback_data budget, and the full overload list live in Callback-Data Patterns.
A complete flow combining both
Pitfalls
- The awaited payload is raw. Values you extract with
ParseArgsorCallbackPatternMatchercome asobject?[]/ typedoutparameters, not handler parameters — cast explicitly. - Always handle
nullfrom the await. Anullmeans the timeout expired or the wait was cancelled; reply with guidance (or silently drop) instead of continuing. - Non-matching updates fall through. While a handler is suspended, other updates for the same user go through normal routing; design your prompts so stray messages don’t derail the flow.
- A suspended handler is single-shot memory. If the bot restarts, suspended flows are gone. Anything that must survive restarts belongs in a persisted state machine instead.