Console applications in C#

Karen Payne - Jan 1 '23 - - Dev Community

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
}


Enter fullscreen mode Exit fullscreen mode

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");
}


Enter fullscreen mode Exit fullscreen mode

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"));


Enter fullscreen mode Exit fullscreen mode

Usage



int result = Prompts.GetInt();


Enter fullscreen mode Exit fullscreen mode

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(),
            }));


Enter fullscreen mode Exit fullscreen mode

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());


Enter fullscreen mode Exit fullscreen mode

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[/]"));


Enter fullscreen mode Exit fullscreen mode

Screen shot

example screenshot for inputs

Menu systems are more of a challenge when using standard code, Spectre.Console to the rescue.

Simple example

example menu

A model for creating a menu



public class MenuItem
{
    public int Id { get; set; }
    public string Text { get; set; }
    public override string ToString() => Text;
}


Enter fullscreen mode Exit fullscreen mode

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;
    }
}


Enter fullscreen mode Exit fullscreen mode

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);
        }

    }
}


Enter fullscreen mode Exit fullscreen mode

Displaying data
Suppose there is a need to display data, Spectre.Console has the ability to display tubular data.

displaying 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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .