Benday.CommandsFramework 4.16.0

dotnet add package Benday.CommandsFramework --version 4.16.0
                    
NuGet\Install-Package Benday.CommandsFramework -Version 4.16.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Benday.CommandsFramework" Version="4.16.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Benday.CommandsFramework" Version="4.16.0" />
                    
Directory.Packages.props
<PackageReference Include="Benday.CommandsFramework" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Benday.CommandsFramework --version 4.16.0
                    
#r "nuget: Benday.CommandsFramework, 4.16.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Benday.CommandsFramework@4.16.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Benday.CommandsFramework&version=4.16.0
                    
Install as a Cake Addin
#tool nuget:?package=Benday.CommandsFramework&version=4.16.0
                    
Install as a Cake Tool

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 --help usage display
  • Dependency injection support
  • Configuration from JSON files, environment variables, and custom sources
  • Arguments that pull values from configuration via FromConfig()
  • Async command support
  • --json schema output for tooling integration
  • gui command to launch a web UI via Benday.CommandsFramework.CmdUi

Table of Contents

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed

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.