Benday.CommandsFramework
4.16.0
dotnet add package Benday.CommandsFramework --version 4.16.0
NuGet\Install-Package Benday.CommandsFramework -Version 4.16.0
<PackageReference Include="Benday.CommandsFramework" Version="4.16.0" />
<PackageVersion Include="Benday.CommandsFramework" Version="4.16.0" />
<PackageReference Include="Benday.CommandsFramework" />
paket add Benday.CommandsFramework --version 4.16.0
#r "nuget: Benday.CommandsFramework, 4.16.0"
#:package Benday.CommandsFramework@4.16.0
#addin nuget:?package=Benday.CommandsFramework&version=4.16.0
#tool nuget:?package=Benday.CommandsFramework&version=4.16.0
Benday.CommandsFramework
A .NET framework for building command-line interface (CLI) utilities. Define named commands with typed, validated arguments using a fluent API, and wire up dependency injection and configuration with minimal boilerplate.
About
Written by Benjamin Day<br>
Pluralsight Author | Microsoft MVP | Scrum.org Professional Scrum Trainer<br>
https://www.benday.com
https://www.honestcheetah.com
info@benday.com
Got ideas for features you'd like to see? Found a bug? Let us know by submitting an issue. Want to contribute? Submit a pull request.
Source code
API Documentation
NuGet Package
Features
- Named commands with descriptions and categories
- Typed arguments:
String,Boolean,Int32,DateTime,File,Directory - Fluent argument definition API with required/optional, default values, and allowed values
- Automatic argument parsing and validation
- Built-in
--helpusage display - Dependency injection support
- Configuration from JSON files, environment variables, and custom sources
- Arguments that pull values from configuration via
FromConfig() - Async command support
--jsonschema output for tooling integrationguicommand to launch a web UI via Benday.CommandsFramework.CmdUi
Table of Contents
- Installation
- Getting Started
- Argument Types
- Configuration
- Dependency Injection
- Async Commands
- Data Formatting Utilities
- CommandsApp Builder Reference
- Built-in Keywords
- About
Installation
dotnet add package Benday.CommandsFramework
Getting Started
1. Create a Command
Commands inherit from SynchronousCommand, AsynchronousCommand, or DependencyInjectionCommand. Use the [Command] attribute to define the command name and description.
using Benday.CommandsFramework;
[Command(Name = "greet",
Description = "Says hello to someone",
Category = "Demo")]
public class GreetCommand : SynchronousCommand
{
public GreetCommand(CommandExecutionInfo info, ITextOutputProvider outputProvider)
: base(info, outputProvider) { }
public override ArgumentCollection GetArguments()
{
var args = new ArgumentCollection();
args.AddString("name").AsRequired().WithDescription("Name of the person to greet");
args.AddBoolean("loud").AsNotRequired().AllowEmptyValue().WithDescription("Greet loudly");
return args;
}
protected override void OnExecute()
{
var name = Arguments.GetStringValue("name");
var loud = Arguments.GetBooleanValue("loud");
var message = $"Hello, {name}!";
WriteLine(loud ? message.ToUpper() : message);
}
}
2. Set Up Program.cs
Use the CommandsApp builder to configure and run your CLI app. Pass any command type from your assembly to Create<T>() — the framework discovers all [Command]-attributed classes in that assembly.
using Benday.CommandsFramework;
CommandsApp
.Create<GreetCommand>(args)
.WithAppInfo("My CLI Tool", "https://www.example.com")
.WithVersionFromAssembly()
.Run();
3. Run It
dotnet run -- greet /name:World
# Output: Hello, World!
dotnet run -- greet /name:World /loud
# Output: HELLO, WORLD!
dotnet run -- greet --help
# Output: Usage information for the greet command
Argument Types
Define arguments using the fluent API in GetArguments():
public override ArgumentCollection GetArguments()
{
var args = new ArgumentCollection();
args.AddString("name").AsRequired().WithDescription("Your name");
args.AddInt32("count").AsRequired().WithDescription("Number of times");
args.AddBoolean("verbose").AsNotRequired().AllowEmptyValue();
args.AddDateTime("start-date").AsRequired().WithDescription("Start date");
args.AddFile("input").AsRequired().WithDescription("Input file path");
args.AddDirectory("output-dir").AsNotRequired().WithDescription("Output directory");
// Restrict to specific values
args.AddString("format").AsRequired().WithAllowedValues("json", "xml", "csv");
// Set a default value
args.AddString("env").AsNotRequired().WithDefaultValue("production");
return args;
}
Arguments are passed on the command line using /name:value syntax. Boolean flags with AllowEmptyValue() can be passed as just /name (presence means true).
Configuration
JSON Files and Environment Variables
CommandsApp
.Create<MyCommand>(args)
.WithAppSettings() // loads appsettings.json + env vars
.WithConfigFile("appsettings.local.json", optional: true) // additional JSON file
.WithEnvironmentVariables() // add env vars explicitly
.Run();
Custom Configuration Sources
Use ConfigureConfiguration() to add any configuration source supported by IConfigurationBuilder, such as in-memory collections:
CommandsApp
.Create<MyCommand>(args)
.WithAppSettings()
.ConfigureConfiguration(config =>
{
config.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string?>("MySection:MyKey", "MyValue")
});
})
.Run();
Config-Backed Arguments
Arguments can pull their values from configuration using FromConfig(). Command-line values take precedence over config values.
public override ArgumentCollection GetArguments()
{
var args = new ArgumentCollection();
args.AddString("api-key")
.FromConfig()
.AsRequired()
.WithDescription("Your API key");
args.AddString("base-url")
.FromConfig()
.AsNotRequired()
.WithDefaultValue("https://api.example.com")
.WithDescription("API base URL");
return args;
}
Dependency Injection
Register services with ConfigureServices() and use them in commands that inherit from DependencyInjectionCommand:
// Program.cs
CommandsApp
.Create<GreetCommand>(args)
.WithAppInfo("My Tool", "https://www.example.com")
.ConfigureServices(services =>
{
services.AddSingleton<IGreetingService, GreetingService>();
})
.Run();
// Or with access to configuration:
CommandsApp
.Create<GreetCommand>(args)
.WithAppSettings()
.ConfigureServices((services, config) =>
{
services.Configure<MyOptions>(config.GetSection("MyOptions"));
services.AddSingleton<IGreetingService, GreetingService>();
})
.Run();
// Command using DI
[Command(Name = "greet", Description = "Greet with DI", IsAsync = true)]
public class GreetCommand : DependencyInjectionCommand
{
public GreetCommand(CommandExecutionInfo info, ITextOutputProvider outputProvider)
: base(info, outputProvider) { }
public override ArgumentCollection GetArguments()
{
var args = new ArgumentCollection();
args.AddString("name").AsRequired().WithDescription("Name to greet");
return args;
}
protected override Task OnExecute()
{
var service = GetRequiredService<IGreetingService>();
WriteLine(service.GetGreeting(Arguments.GetStringValue("name")));
return Task.CompletedTask;
}
}
Async Commands
For commands that need async operations, inherit from AsynchronousCommand:
[Command(Name = "fetch", Description = "Fetch data from API", IsAsync = true)]
public class FetchCommand : AsynchronousCommand
{
public FetchCommand(CommandExecutionInfo info, ITextOutputProvider outputProvider)
: base(info, outputProvider) { }
public override ArgumentCollection GetArguments()
{
var args = new ArgumentCollection();
args.AddString("url").AsRequired().WithDescription("URL to fetch");
return args;
}
protected override async Task OnExecute()
{
var url = Arguments.GetStringValue("url");
// async work here
await Task.CompletedTask;
}
}
CommandsApp Builder Reference
| Method | Description |
|---|---|
Create<TCommand>(args) |
Create builder, discover commands from the assembly containing TCommand |
Create(args, assembly) |
Create builder with explicit assembly |
WithAppInfo(name, website) |
Set application name and website |
WithAppInfo(name, version, website) |
Set application name, version, and website |
WithVersion(version) |
Set version string |
WithVersionFromAssembly() |
Auto-detect version from assembly file version |
WithAppSettings(optional) |
Load appsettings.json and environment variables |
WithConfigFile(filename, optional) |
Load an additional JSON config file |
WithEnvironmentVariables() |
Add environment variables to configuration |
ConfigureConfiguration(action) |
Direct access to IConfigurationBuilder for custom sources |
ConfigureServices(action) |
Register services for dependency injection |
ConfigureServices(action<services, config>) |
Register services with access to IConfiguration |
ConfigureOptions(action) |
Configure DefaultProgramOptions directly |
ConfigureUsageDisplay(action) |
Configure how usage/help is displayed |
UsesConfiguration(bool) |
Enable/disable built-in configuration storage |
Run() |
Build and run the application |
RunAsync() |
Build and run the application asynchronously |
Data Formatting Utilities
The framework includes utility classes in Benday.CommandsFramework.DataFormatting for working with tabular and CSV data inside your commands.
TableFormatter
Format data as aligned, column-padded tables for console output. Supports optional row filtering.
using Benday.CommandsFramework.DataFormatting;
var formatter = new TableFormatter();
formatter.AddColumn("Name");
formatter.AddColumn("Role");
formatter.AddColumn("Location");
formatter.AddData("Alice", "Developer", "Seattle");
formatter.AddData("Bob", "Designer", "Portland");
formatter.AddData("Carol", "Manager", "Denver");
WriteLine(formatter.FormatTable());
Output:
Name Role Location
Alice Developer Seattle
Bob Designer Portland
Carol Manager Denver
Use AddDataWithFilter() to only include rows where any column value contains a search string (case-insensitive):
formatter.AddDataWithFilter("port", "Bob", "Designer", "Portland"); // included
formatter.AddDataWithFilter("port", "Alice", "Developer", "Seattle"); // excluded
CsvReader
Read and iterate over CSV files or strings. Supports header rows, quoted values with embedded commas and newlines, and column access by name or index.
using Benday.CommandsFramework.DataFormatting;
// From a file
var reader = CsvReader.FromFile("/path/to/data.csv");
// Or from a string
var reader = new CsvReader("Name,Age,City\nAlice,30,Seattle\nBob,25,Portland");
foreach (var row in reader)
{
// Access by column name
var name = row["Name"];
var age = row["Age"];
// Or by index
var city = row[2];
Console.WriteLine($"{name} is {age} years old and lives in {city}");
}
CsvWriter
Build CSV data in memory, edit existing CSV content, and write to file or string. Handles quoting of values that contain commas, newlines, or quotes.
using Benday.CommandsFramework.DataFormatting;
// Create from scratch
var writer = new CsvWriter();
writer.AddColumns("Name", "Age", "City");
writer.AddRow("Alice", "30", "Seattle");
writer.AddRow("Bob", "25", "Portland");
// Save to file
writer.SaveToFile("/path/to/output.csv");
// Or get as string
var csvString = writer.ToCsvString();
Edit existing CSV data by loading from a CsvReader:
var reader = CsvReader.FromFile("/path/to/data.csv");
var writer = new CsvWriter(reader);
// Modify a value
writer.SetValue(0, "City", "Tacoma");
// Add a new row
writer.AddRow("Carol", "35", "Denver");
// Remove a row
writer.RemoveRow(1);
writer.SaveToFile("/path/to/updated.csv");
Built-in Keywords
--help— Display usage information for a command--json— Output the full command schema as JSON (used by tooling)gui— Launch the CmdUi web interface for this tool
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.14)
- Microsoft.Extensions.Hosting (>= 9.0.14)
- Microsoft.Extensions.Logging (>= 9.0.14)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.14)
- Microsoft.Extensions.Hosting (>= 9.0.14)
- Microsoft.Extensions.Logging (>= 9.0.14)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.14)
- Microsoft.Extensions.Hosting (>= 9.0.14)
- Microsoft.Extensions.Logging (>= 9.0.14)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Benday.CommandsFramework:
| Package | Downloads |
|---|---|
|
Benday.AzureDevOpsUtil.Api
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.16.0 | 120 | 3/13/2026 |
| 4.15.0 | 93 | 3/7/2026 |
| 4.14.1 | 95 | 3/5/2026 |
| 4.14.0 | 105 | 3/4/2026 |
| 4.13.0 | 334 | 11/5/2025 |
| 4.12.0 | 185 | 9/27/2025 |
| 4.11.0 | 167 | 9/26/2025 |
| 4.10.0 | 284 | 8/9/2025 |
| 4.9.0 | 342 | 6/4/2025 |
| 4.8.0 | 222 | 5/11/2025 |
| 4.7.0 | 332 | 4/16/2025 |
| 4.6.0 | 222 | 3/21/2025 |
| 4.5.0 | 184 | 3/15/2025 |
| 4.4.0 | 377 | 11/20/2024 |
| 4.3.0 | 278 | 8/19/2024 |
| 4.2.0 | 261 | 8/16/2024 |
| 4.1.1 | 225 | 8/8/2024 |
| 4.1.0 | 209 | 8/8/2024 |
| 4.0.0 | 215 | 7/30/2024 |
| 4.0.0-beta | 122 | 7/29/2024 |
v4.16 - Added ConfigureConfiguration() method to CommandsApp builder for adding custom configuration sources (in-memory collections, additional JSON files, environment variables, etc.);
v4.15 - Added StrictArgumentValidation option to control whether unknown arguments cause validation failures (defaults to false for backward compatibility);
v4.14 - Added support for .NET 10.0; Added support for 'gui' command that launches a blazor UI for the tool using the Benday.CommandsFramework.CmdUi package; Added fluent config methods and simplified DI configuration; Added support for defining allowed argument values; Added support for defining args that pull their values from a config value;
v4.13 - Added support for writing CSV files via CsvWriter utility class;
v4.12 - Improved dependency injection support for commands;
v4.11 - Added option to get file and directory arguments as fully qualified paths;
v4.10 - Added support for .NET 9.0; Added CsvReader utility class for parsing CSV files;
v4.9 - Added ability to pass in an IServiceCollection to the commands framework via IProgramOptions in order to optionally allow commands to use dependency injection;
v4.8 - Changed visibility of runtime args collection; Added utility method to pull value from args collection or config collection;
v4.7 - Fixed bug getting --help for commands with no parameters.
v4.6 - Added support for .NET Core 9.
v4.5 - Added TableFormatter to help with formatting tabular data and filtered tabular data.
v4.4 - Added FileArgument and DirectoryArgument types.
v4.3 - Added support for GetDate -Format FileDateUniversal datetime parsing.
v4.2 - Added support for more datetime files in the DateTime argument type.
v4.1.1 - Fixed bug in DefaultProgram where it was not setting the ExitCode to 1 when there was an error.
v4.1 - Added '--help' option to default program implementation in order to display usages.
v4.0 - Refactored argument base classes to remove unnecessary constructors. Added option to get all usages in JSON format using '--json' option.
v3.4 - Changed 'display usage' on commands to return ExitCode of 1 if there is an invalid/missing parameter.
v3.3 - Fixed 'display usage' formatting bug when argument does not have a description. Added logic to DefaultProgram to set ExitCode to 1 automatically on error.
v3.2 - Bug fixes.
v3.1 - Bug fixes. Added ability to inject an instance of ITextOutputProvider for testability.
v3.0 - Upgraded to .NET 8.0. Added default commands for managing basic string configuration values. Added extension methods for working with relative paths as arguments.