Modern.Console.Engine
3.1.52
dotnet add package Modern.Console.Engine --version 3.1.52
NuGet\Install-Package Modern.Console.Engine -Version 3.1.52
<PackageReference Include="Modern.Console.Engine" Version="3.1.52" />
<PackageVersion Include="Modern.Console.Engine" Version="3.1.52" />
<PackageReference Include="Modern.Console.Engine" />
paket add Modern.Console.Engine --version 3.1.52
#r "nuget: Modern.Console.Engine, 3.1.52"
#:package Modern.Console.Engine@3.1.52
#addin nuget:?package=Modern.Console.Engine&version=3.1.52
#tool nuget:?package=Modern.Console.Engine&version=3.1.52
IVolt Modern Console Library
Package: ModernConsoleLib
Version: 3.1.5
Namespace: IVolt.Tools.ModernConsole
Target Framework: .NET 10.0
License: AGPL-3.0-or-later
Author: Mark Alicz — Mark Alicz
Website: https://github.com/MarkAlicz/Modern-Console-Engine
Overview
ModernConsoleLib upgrades .NET console applications from raw Console.ReadLine() workflows into a structured, navigable UI without pulling in a UI framework. It provides a page/navigation model, fully interactive arrow-key menus with text-type-ahead, colored output, structured input helpers, JSON-driven menu configuration, external plugin dispatch via a clean interface, and shell command execution — all AOT-compatible and zero external dependencies beyond the BCL.
Installation
<PackageReference Include="ModernConsoleLib" Version="3.1.5" />
Or reference the assembly directly:
<Reference Include="ModernConsoleLib" />
Architecture
The library is organized around five cooperating layers:
Program (abstract host, page registry, navigation history)
└── Page (abstract display unit with breadcrumb support)
└── MenuPage (Page + embedded Menu)
└── Menu (interactive option list, JSON-loadable)
└── Option (id / name / value / SubMenu)
Input — structured console input, menu selection engine
Output — color-safe WriteLine/prompt helpers
MenuNavigation — static UI config (colors, sounds)
CmdExecutor — synchronous shell command execution
IMenuOptionHandler — plugin dispatch contract
Quick Start
Code-Defined Menu
using IVolt.Tools.ModernConsole;
using IVolt.Tools.ModernConsole.Interfaces;
// 1. Implement the handler
public class MyHandler : IMenuOptionHandler
{
public object Handle(Menu menu, Option option, string idOrName)
{
switch (idOrName)
{
case "run_report": RunReport(); break;
case "settings": ShowSettings(); break;
}
return null;
}
}
// 2. Build and display the menu
var menu = new Menu { Title = "Main Menu" };
menu.Add("run_report", "Run Report");
menu.Add("settings", "Settings");
menu.OptionHandler = new MyHandler();
menu.Display();
JSON-Driven Menu
{
"title": "Main Menu",
"handler": {
"dllPath": "###BASEPATH###MyHandler.dll",
"type": "MyApp.MyHandler"
},
"navigation": {
"highlightForeground": "Black",
"highlightBackground": "Cyan",
"soundsEnabled": false
},
"options": [
{ "id": "run_report", "name": "Run Report" },
{
"id": "tools",
"name": "Tools",
"submenu": {
"title": "Tools",
"options": [
{ "id": "export", "name": "Export Data" },
{ "id": "import", "name": "Import Data" }
]
}
}
]
}
string json = File.ReadAllText("menu.json");
var menu = Menu.LoadFromJSON(json);
menu.Display();
###BASEPATH### is automatically replaced with AppDomain.CurrentDomain.BaseDirectory at load time.
Core Types
Program (abstract)
The application host. Subclass it to create your console app.
public class MyApp : Program
{
public MyApp() : base("My Application", breadcrumbHeader: true)
{
AddPage(new MainMenuPage(this));
SetPage<MainMenuPage>();
}
}
new MyApp().Run();
| Member | Description |
|---|---|
Title |
Sets Console.Title on Run() |
BreadcrumbHeader |
Shows navigation path in page headers |
History |
Stack<Page> — current navigation state |
NavigationEnabled |
True when History depth > 1 |
ArrowNavigationEnabled |
Toggle arrow-key vs numeric fallback |
AddPage(Page) |
Register a page by type |
SetPage<T>() |
Push page T onto history (no render) |
NavigateTo<T>() |
Push + render page T |
NavigateBack() |
Pop history + render previous page |
NavigateHome() |
Unwind to root + render |
Run() |
Entry point — renders CurrentPage, handles top-level exceptions |
ReadMenuSelection(...) |
Inline arrow-key or numeric menu picker |
RenderOptions(...) |
Low-level option list renderer (static) |
Page (abstract)
A discrete screen in the application.
public class MainMenuPage : Page
{
public MainMenuPage(Program program) : base("Main Menu", program) { }
public override void Display()
{
base.Display(); // prints title or breadcrumb + "---"
// your rendering logic
}
}
base.Display() renders either a breadcrumb trail (Home > Section > Current) or just the page title depending on Program.BreadcrumbHeader.
MenuPage (abstract)
A Page with a Menu pre-wired. Adds "Go back" automatically when navigation is enabled.
public class ToolsPage : MenuPage
{
public ToolsPage(Program program)
: base("Tools", program,
new Option("export", "Export"),
new Option("import", "Import"))
{
Menu.OptionHandler = new MyHandler();
}
}
Menu
The interactive option list engine.
var menu = new Menu { Title = "Choose" };
// Fluent adds
menu.Add("Display Name"); // name only (id = null)
menu.Add("my_id", "Display Name"); // id + name
menu.Add("my_id", "Display Name", "payload"); // id + name + value
menu.Add(new Option { Id = "x", Name = "X", SubMenu = subMenu });
menu.OptionHandler = new MyHandler();
menu.Display(); // blocking — runs the full interaction loop
Navigation behavior during Display():
- ESC on a sub-menu → returns to parent menu
- ESC on root → prompts Y/N exit confirmation
- "Go Back" option → same as ESC
- "Exit to Primary Menu" option → unwinds all sub-menus to root
- Nested sub-menus automatically inherit the parent
OptionHandlerunless they define their own
Static helpers:
// Check or retrieve by id or name
bool found = menu.Contains("my_id");
Option opt = menu.GetMenuOption("my_id");
// JSON load
Menu m = Menu.LoadFromJSON(jsonString);
Option
public class Option
{
public string? Id { get; set; } // dispatch key; falls back to Name
public string Name { get; set; } // display text
public string Value { get; set; } // arbitrary payload
public Menu? SubMenu { get; set; } // nested menu
}
Input (static)
Structured console input. All numeric inputs loop until valid.
int n = Input.ReadInt("Enter count:", min: 1, max: 100);
int n = Input.ReadInt(1, 100); // no prompt overload
int n = Input.ReadInt(); // unbounded
string s = Input.ReadString("Enter name:");
// Full interactive menu picker (used internally by Menu.Display)
// Returns 0-based index, or -1 if user pressed ESC
int choice = Input.ReadMenuChoice("Choose:", optionNames, optionsTop);
// Enum-driven menu
MyEnum val = Input.ReadEnum<MyEnum>("Select mode:");
ReadMenuChoice supports three simultaneous input modes — arrow keys, numeric entry (1..N), and text type-ahead (exact match → unique prefix → unique contains). All three are active simultaneously; the user can switch freely mid-selection.
Output (static)
Color-safe output. Always calls Console.ResetColor() after writing.
Output.WriteLine(ConsoleColor.Green, "Success: {0}", result);
Output.WriteLine(ConsoleColor.Red, "Error: something failed");
Output.WriteLine("Plain text: {0}", value); // no color
Output.DisplayPrompt("Enter value:"); // Console.Write, no newline, trims + adds space
MenuNavigation (static)
Global UI configuration. Set once at startup or load from JSON navigation block.
MenuNavigation.HighlightForeground = ConsoleColor.Black;
MenuNavigation.HighlightBackground = ConsoleColor.Cyan;
MenuNavigation.NormalForeground = ConsoleColor.Gray; // null = Console.ResetColor()
MenuNavigation.NormalBackground = null;
MenuNavigation.SoundsEnabled = true;
MenuNavigation.SoundOnMove = true;
MenuNavigation.SoundOnSelect = true;
MenuNavigation.SoundOnInvalid = false;
// Beep tuning (only used when SoundHandler is null)
MenuNavigation.MoveBeepFrequency = 800;
MenuNavigation.MoveBeepDurationMs = 20;
MenuNavigation.SelectBeepFrequency = 1200;
MenuNavigation.SelectBeepDurationMs = 35;
// Custom sound handler (overrides Console.Beep entirely)
MenuNavigation.SoundHandler = ev => {
if (ev == MenuSoundEvent.Move) PlayClick();
};
MenuSoundEvent values: Move, Select, Invalid.
Plugin System
External handlers are loaded at runtime via IMenuOptionHandler. The handler DLL does not need to ship with the host — it is resolved and loaded on first menu display.
Define the handler (in a separate DLL):
// MyHandler.dll — references ModernConsoleLib
public class AppMenuHandler : IMenuOptionHandler
{
public object Handle(Menu menu, Option option, string idOrName)
{
switch (idOrName)
{
case "export": ExportData(option.Value); break;
case "import": ImportData(); break;
default:
Output.WriteLine(ConsoleColor.Yellow, $"Unknown option: {idOrName}");
break;
}
return null;
}
}
Requirements: public class, parameterless public constructor, implements IMenuOptionHandler.
Wire via JSON:
{
"handler": {
"dllPath": "###BASEPATH###MyHandler.dll",
"type": "MyApp.AppMenuHandler"
}
}
Wire in code:
menu.OptionHandler = new AppMenuHandler();
Handler inheritance: sub-menus automatically inherit the parent menu's handler unless they explicitly override it with their own handler block in JSON or set OptionHandler in code.
Shell Command Execution
CmdExecutor runs synchronous cmd.exe commands with captured stdout/stderr.
using IVolt.Tools.ModernConsole.CommandHelper;
CommandResult result = CmdExecutor.Execute("dir /b C:\\temp");
if (result.ExitCode == 0)
{
foreach (string line in result.StandardOutput)
Output.WriteLine(ConsoleColor.White, line);
}
else
{
foreach (string err in result.StandardError)
Output.WriteLine(ConsoleColor.Red, err);
}
CommandResult properties: int ExitCode, List<string> StandardOutput, List<string> StandardError.
The process runs hidden (CreateNoWindow = true, WindowStyle = Hidden) with shell execute disabled. stdout and stderr are captured asynchronously via BeginOutputReadLine / BeginErrorReadLine and the call blocks on WaitForExit().
Utilities
StringExtensions
Adds a .Format() extension to string for syntactic convenience:
string msg = "Value is {0}".Format(someValue);
// equivalent to string.Format("Value is {0}", someValue)
JSON Schema Reference
Full menu JSON structure:
{
"title": "string — displayed as menu header",
"handler": {
"dllPath": "string — path to handler DLL; ###BASEPATH### = app base dir",
"type": "string — fully qualified type name"
},
"navigation": {
"highlightForeground": "ConsoleColor name string, e.g. Black",
"highlightBackground": "ConsoleColor name string, e.g. Cyan",
"normalForeground": "ConsoleColor name string or omit for reset",
"normalBackground": "ConsoleColor name string or omit for reset",
"soundsEnabled": false,
"soundOnMove": true,
"soundOnSelect": true,
"soundOnInvalid": false,
"moveBeepFrequency": 800,
"moveBeepDurationMs": 20,
"selectBeepFrequency": 1200,
"selectBeepDurationMs": 35,
"invalidBeepFrequency": 200,
"invalidBeepDurationMs": 80
},
"options": [
{
"id": "string — dispatch key (optional, falls back to name)",
"name": "string — display text (required)",
"value": "string — arbitrary payload, surfaced to the handler as Option.Value (optional)",
"submenu": { /* recursive MenuJson */ }
}
]
}
The navigation block is only applied at the root level and configures global MenuNavigation statics. It is ignored in nested sub-menu JSON objects.
JSON deserialization is case-insensitive (PropertyNameCaseInsensitive = true).
Legacy API
MenuActionRegistry is retained for backward compatibility but marked [Obsolete]. Do not use in new code. The replacement is Menu.OptionHandler / IMenuOptionHandler.
// Old pattern — DEPRECATED
MenuActionRegistry.Register("my_action", () => DoSomething());
// New pattern
menu.OptionHandler = new MyHandler(); // centralized dispatch
AOT Compatibility
The project is built with <IsAotCompatible>true</IsAotCompatible> for both Debug and Release configurations. The plugin loader uses Assembly.LoadFrom and reflection for handler instantiation — this path is inherently dynamic and will require appropriate rd.xml / trim annotations if you publish with PublishTrimmed or PublishAot and use the plugin system. Code-defined handlers with menu.OptionHandler = new MyHandler() are fully AOT-safe.
Notes and Caveats
Console.Beep is wrapped in a try/catch and silently swallowed — the library will not throw on platforms or environments where beep is unavailable (redirected output, CI runners, some terminal emulators).
CmdExecutor is Windows-only (cmd.exe). For cross-platform shell execution, replace with ProcessStartInfo { FileName = "/bin/bash", Arguments = $"-c \"{command}\"" }.
Input.ReadMenuChoice returns -1 on ESC. Callers should handle this explicitly if ESC-as-cancel semantics are needed outside of Menu.Display().
Breadcrumb rendering in Page.Display() uses Program.History which is a Stack<Page> — it reflects push order, not page titles in definition order. Ensure pages are pushed in logical navigation order for breadcrumbs to read correctly.
License
AGPL-3.0-or-later. License acceptance is required for NuGet consumption (PackageRequireLicenseAcceptance = true).
© 2026 Mark Alicz
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.1.52 | 39 | 8/18/2026 |