Console applications have been around for a long time and still hold a place in todays world were web applications are predominate type of applications.
They are good for
- Learning how to code
- Create useful utilities
- Used as CLI tools
Full source code with 84 projects.
For the novice developer they tend to struggle with basic input and creation of menus let alone working with the file system and databases.
Suppose a developer wants to obtain a number from the user, the following is generally used.
int input = 0;
try
{
input = Convert.ToInt32(Console.ReadLine());
}
catch
{
//code here
}
They do not consider what if a user enters a non-numeric value. This means assertion is required, in this case using int.TryParse or int.TryParse etc.
var userInput = Console.ReadLine();
if (int.TryParse(userInput, out var input))
{
Console.WriteLine($"Value from ReadLine: {input}");
}
else
{
Console.WriteLine($"'{userInput}' can not represent a int");
}
Let's look at an even better way to capture numeric input using a open source library, Spectre.Console.
public static int GetInt() =>
AnsiConsole.Prompt(
new TextPrompt<int>("Enter a [green]number[/] between [b]1[/] and [b]10[/]")
.PromptStyle("green"));
Usage
int result = Prompts.GetInt();
If the wrong type is entered a standard message appears and prompts for a value again. Suppose the number must be in a range, we can add validation as per below with a range of 1-10.
public static int GetInt() =>
AnsiConsole.Prompt(
new TextPrompt<int>("Enter a [green]number[/] between [b]1[/] and [b]10[/]")
.PromptStyle("green")
.ValidationErrorMessage("[red]That's not a valid age[/]")
.Validate(age => age switch
{
<= 0 => ValidationResult.Error("[red]1 is min value[/]"),
>= 10 => ValidationResult.Error("[red]10 is max value[/]"),
_ => ValidationResult.Success(),
}));
What about DateTime? The following uses the same pattern as above but typed to DateTime with specific validation along with allowing the user not to enter a date.
public static DateTime? GetBirthDate() =>
AnsiConsole.Prompt(
new TextPrompt<DateTime>("What is your [white]birth date[/]?")
.PromptStyle("yellow")
.ValidationErrorMessage("[red]Please enter a valid date or press ENTER to not enter a date[/]")
.Validate(dateTime => dateTime.Year switch
{
>= 2001 => ValidationResult.Error("[red]Must be less than 2001[/]"),
_ => ValidationResult.Success(),
})
.AllowEmpty());
To kept code consistent use a similar method for strings.
public static string GetFirstName() =>
AnsiConsole.Prompt(
new TextPrompt<string>("[white]First name[/]?")
.PromptStyle("yellow")
.AllowEmpty()
.Validate(value => value.Trim().Length switch
{
< 3 => ValidateFirstName(),
_ => ValidationResult.Success(),
})
.ValidationErrorMessage("[red]Please enter your first name[/]"));
Screen shot
Menu systems are more of a challenge when using standard code, Spectre.Console to the rescue.
Simple example
A model for creating a menu
public class MenuItem
{
public int Id { get; set; }
public string Text { get; set; }
public override string ToString() => Text;
}
Create the menu
public class MenuOperations
{
private static Style HighLightStyle => new(
Color.LightGreen,
Color.Black,
Decoration.None);
public static SelectionPrompt<MenuItem> MainMenu()
{
SelectionPrompt<MenuItem> menu = new()
{
HighlightStyle = HighLightStyle
};
menu.Title("Select an [B]option[/]");
menu.AddChoices(new List<MenuItem>()
{
new() { Id = 0, Text = "List employees"},
new() { Id = 1, Text = "Add manager"},
new() { Id = 2, Text = "Add engineer"},
new() { Id = 3, Text = "Add employee"},
new() { Id = 4, Text = "Edit an employee "},
new() { Id = 5, Text = "Delete an employee "},
new() { Id = 6, Text = "Save all"},
new() { Id = -1, Text = "Exit"},
});
return menu;
}
public static SelectionPrompt<Employee> RemoveMenu(List<Employee> list)
{
SelectionPrompt<Employee> menu = new()
{
HighlightStyle = HighLightStyle
};
menu.Title("Select an employee to [B]remove[/] or select [B]return to menu[/] to abort.");
menu.AddChoices(list);
return menu;
}
}
Then in Main method of the project setup the menu in a loop.
using MenuConsoleAppBasic.Classes;
using MenuConsoleAppBasic.Models;
using Spectre.Console;
namespace MenuConsoleAppBasic;
partial class Program
{
static void Main(string[] args)
{
MenuItem menuItem = new MenuItem();
List<Employee> employeesList = Operations.ReadEmployees();
while (menuItem.Id > -1)
{
AnsiConsole.Clear();
AnsiConsole.Write(
new Panel(new Text("Some fictitious company HR").Centered())
.Expand()
.SquareBorder()
.BorderStyle(new Style(Color.Cornsilk1))
.Header("[LightGreen]About[/]")
.HeaderAlignment(Justify.Center));
menuItem = AnsiConsole.Prompt(MenuOperations.MainMenu());
Selection(menuItem, employeesList);
}
}
}
Displaying data
Suppose there is a need to display data, Spectre.Console has the ability to display tubular data.
My NuGet package
SpectreConsoleLibrary contains useful methods for working with console projects. GitHub repository
Summary
By using open source library like Spectre.Console creating useful console applications easy. Spectre.Console also makes it easy to create dotnet tools, see documentation and check out their GitHub repository.
All source code for what has been presented and more can be found here.
Note The tile image source code is provided in the source code