Console applications (or command line interfaces – CLI) have long been a staple for developers due to their simplicity and ease of use. However, with the right tools and techniques, you can build sophisticated console applications that are both interactive and user-friendly.
Let’s explore how to create advanced console applications in C# using Spectre.Console.Cli for command parsing, logging, and interactive user input.
Setting Up Your Project
First, let’s set up a new console application and add the necessary libraries.
- Create a New Console Application: Open your terminal or command prompt and run the following commands:
dotnet new console -n InteractiveConsoleAppcd InteractiveConsoleApp - Add Required Packages: We’ll use
Spectre.Console.Clifor command parsing andSpectre.Consolefor rich user interfaces.dotnet add package Spectre.Console.Clidotnet add package Spectre.Console
Implementing Command Parsing with Spectre.Console.Cli
Command parsing is essential for creating interactive console applications. It allows you to define and handle different commands and their arguments.
Defining Commands
Create a new class named GreetCommand.cs to define your greet command.
// GreetCommand.cs
using Spectre.Console;
using Spectre.Console.Cli;
using System.ComponentModel;
public class GreetCommand : Command<GreetCommand.Settings>
{
public class Settings : CommandSettings
{
[CommandOption("-n|--name")]
[Description("The name of the user")]
public string Name { get; set; }
}
public override int Execute(CommandContext context, Settings settings)
{
if (string.IsNullOrEmpty(settings.Name))
{
settings.Name = AnsiConsole.Ask<string>("What's your [green]name[/]?");
}
AnsiConsole.MarkupLine($"Hello, [bold yellow]{settings.Name}[/]!");
return 0;
}
}
Setting Up the Main Program
Modify Program.cs to set up and run the command parser with a message loop.
// Program.cs
using Spectre.Console;
using Spectre.Console.Cli;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
class Program
{
static void Main(string[] args)
{
// Create a host
var host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddConsole();
})
.ConfigureServices((context, services) =>
{
services.AddSingleton<ITypeRegistrar, TypeRegistrar>();
})
.Build();
var app = new CommandApp(new TypeRegistrar(host.Services));
app.Configure(config =>
{
config.AddCommand<GreetCommand>("greet");
});
var logger = host.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Starting application");
try
{
bool keepRunning = true;
while (keepRunning)
{
var choice = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("What do you want to do?")
.PageSize(10)
.AddChoices(new[] { "Greet", "Exit" }));
switch (choice)
{
case "Greet":
app.Run(new string[] { "greet" });
break;
case "Exit":
AnsiConsole.MarkupLine("[bold red]Goodbye![/]");
keepRunning = false;
break;
}
}
}
catch (Exception ex)
{
logger.LogError(ex, "An unhandled exception occurred");
}
finally
{
logger.LogInformation("Shutting down application");
}
}
}
public class TypeRegistrar : ITypeRegistrar
{
private readonly IServiceCollection _services;
public TypeRegistrar(IServiceCollection services)
{
_services = services;
}
public ITypeResolver Build()
{
return new TypeResolver(_services.BuildServiceProvider());
}
public void Register(Type service, Type implementation)
{
_services.AddSingleton(service, implementation);
}
public void RegisterInstance(Type service, object implementation)
{
_services.AddSingleton(service, implementation);
}
public void RegisterLazy(Type service, Func<object> factory)
{
_services.AddSingleton(service, (provider) => factory());
}
}
public class TypeResolver : ITypeResolver
{
private readonly IServiceProvider _provider;
public TypeResolver(IServiceProvider provider)
{
_provider = provider;
}
public object Resolve(Type type)
{
return _provider.GetRequiredService(type);
}
}
Code Breakdown
Creating a Host:
The host is a central point of application configuration and control.
Host.CreateDefaultBuilder(args)initializes the host builder with default settings.ConfigureLoggingclears any existing logging providers and adds the console logging provider.ConfigureServicesregisters a singleton service forITypeRegistrarusingTypeRegistrar.Build()builds and returns the configured host.
Setting Up CommandApp:
- CommandApp: This sets up a command-line application using
Spectre.Console.Cli. - TypeRegistrar: A custom type registrar that handles dependency injection with the ConsoleApp.
- Configure: A command configuration method that adds a
greetcommand which is defined in theGreetCommandclass.
Message Loop:
- Continuously prompts the user with a menu until they choose to exit.
AnsiConsole.Promptdisplays a menu with two options.- Switch Statement:
Greet: When this option is chosen, we use Spectre.Console to find and run thegreetcommand.Exit: Displays a goodbye message and setskeepRunningtofalseto exit the loop.
- Exception Handling: Wrap the whole message loop in a try-catch to handle any expection thrown by commands. It currently logs any unhandled exceptions and exits and program.
- Finally: Logs that the application is shutting down.
Running the Application
You can now run the application and interact with it through the menu:
dotnet run
Conclusion
By combining Spectre.Console.Cli and Spectre.Console, you can create sophisticated and interactive console applications in C#. These libraries allow you to handle command parsing and user interactions seamlessly, making your console applications more robust and user-friendly.