How YOU can build a great looking .NET Console app with Spectre
Tags: dotnet, tutorial, csharp, spectre
Published: 2022-02-17 00:12:00

---
title: How YOU can build a great looking .NET Console app with Spectre
published: true
description: This article shows how to build a fancy looking console app that your users will want to interact with
tags: dotnet, tutorial, csharp, spectre
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bomocdtjoio222od149p.png
---

> This article will show how to build a great looking console app that your users will love interacting with. The console app will contain some graphical widgets that will make for a great user experience.

## Why console apps

There are many types of apps, mobile apps, web apps, console apps, games and so on. Many of them have really great looking UIs some doesn't need UIs to run. So, is there a need for console apps, apps that run in your terminal with seemingly little or no graphical interface? In short yes, here's some cases:

- **Scripting**. First of all, not all apps require a user to function. For example, as part of a CI/CD pipeline there's an agent that performs various steps. One of those steps could be executing an app. Such an app is usually a console app that may take command line arguments as data input, and operate on those. Here, there shouldn't be a graphical interface, as it would stop everything up and wait for a user to interact.
- **Batch processing**. There are many apps out there that are really good at working on batches of data and all it wants from you is for you to provide input and it does the rest. That input can be a file directory, a URL or something else, no need for a user to click a button.

### It's a console app, it's ok if the UI is bad?

It's easy to have the opinion that a console app doesn't need a good-looking interface, especially if it's of the scripting or batch processing types we mentioned above. However, some console apps, might warrant a better-looking interface if a user is meant to interact with it. From a user's standpoint, what would the requirements be of a such an interface? Here's some ideas:

- **Correctness**. As a user you need the program to help you input the correct data, that could be the correct name of things, or from a limited list of options and so on.
- **Feedback**. As a user, you want to know that the program has correctly understood my input but also that it's currently working on something and when it's done "doing its thing". What you don't want is a program that says *nothing*, as you might interpret that as malfunctioning or that you need to sit and watch it until it finishes
- **Read support**. You want to help the user by highlighting certain keywords, especially if you plan to describe something in text. This ensures the user understands what they're supposed to do.

Ok, so we have a case for why we should care about the user interface of a console app, what are my options?

In this article, we will look at [Spectre.Console](https://spectreconsole.net/quick-start), a very competent library for helping us not only with the user experience with various graphical widgets but that also contain methods for helping us process command-line input.

## References

Here's interesting links that might help you to learn more:

- [Building a tool](https://docs.microsoft.com/dotnet/core/tools/global-tools-how-to-create)
- [Globally installing a tool](https://docs.microsoft.com/dotnet/core/tools/global-tools-how-to-use)
- [Spectre.Console lib](https://spectreconsole.net/quick-start)

## Spectre, a high-level view

At high-level, Spectre.Console contains two major things:

- **Graphical widgets**, Spectre.Console. Here we have features like:
  - **Prompts**, this is a great feature as it allows you to prompt the user for input, both single input and selection/s from a list
  - **Status, Progress**. By having these, you're able to convey the user how the program is doing, so you know whether you can leave the program to do it's thing if it's working or if it's stuck.
  - **Coloration**, this allows you to set certain colors to the text to ensure you can differentiate between different types of messages like errors, logs and also highlight keywords.
  - **Widgets, Table, Tree, BarCharts and even a Canvas**. Anyone wants to build Pacman in the console, here's your chance :)
- **Command line processing**, Spectre.Console.Cli

## Exercise - Hello Spectre

Here, we will start with the example highlighted in the quick start. It may look simple, but what you're getting is quite useful.

1. Run `dotnet new` in your terminal to scaffold a dotnet project.

   ```bash
   dotnet new console -o spectre-demo
   cd spectre-demo
   ```

1. Run `dotnet add package` to add the Spectre.Console package to your project:

   ```bash
   dotnet add package Spectre.Console
   ```

1. Add the following code to your `Program.cs`:

   1. At the top, add using directive:

   ```csharp
   using Spectre.Console;
   ```

   1. Lower down, add these lines:

   ```csharp
   AnsiConsole.Markup("[underline red]Hello[/] World!");

   AnsiConsole.Markup("[green]This is all green[/]");
   ```

   Now you have a program using Spectre.Console. Lets run it next, to see what it does:

1. Run `dotnet run`, to build and run your app:

   ```bash
   dotnet run
   ```

   You should see an output looking like this text:

   ![Spectre output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cz62bkco0jzoc0q9djxw.png)

   What you are seeing is Spectre's ability to color the output. It works in the following way:

   1. You call the `Markup()` method. It expects markup that it can turn into colors. The markup is defined like so:
   
   ```csharp
   [color format]some text[/]
   ```

   Here's an example:

   ```csharp
   AnsiConsole.Markup("[underline red]Hello[/] World!");
   ```
  
   The above text "Hello" will be made "underlined" and "red".

## Exercise - build a scaffolder app

So far, you've learned how you can add colors and other formatting, to part of a string.

### User input

There's more, you can also capture user input. For that you will use the `Confirm()` method. `Confirm()` will present itself as an input field asking the user for 'y' or 'n'. The response will be stored as a boolean. Here's an example:

```csharp
bool saveFile = AnsiConsole.Confirm("Save file?");
```

Let's decide on working on a bit more advanced project, so clear all previous code and let's start fresh. Our project will be able to scaffold files we might need for a GitHub repo.

1. Add the following code to prompt the user:

   ```csharp
   var answerReadme = AnsiConsole.Confirm("Generate a [green]README[/] file?");

   var answerGitIgnore = AnsiConsole.Confirm("Generate a [yellow].gitignore[/] file?");
   ```

   Great, now lets see what that looks like, rerun with `dotnet run`:

   ![prompt](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ja5110q1r16ppglr204.png)

   Ok, great, we can capture user input.

### Capture user input from a selection

Sometimes you want the user to select from a list of options. This is to ensure the user provides valid input. For this, you can use `Prompt()`. The idea is to provide a list of choices. The selected choice is returned to you.

```csharp
var framework = AnsiConsole.Prompt(
  new SelectionPrompt<string>()
      .Title("Select [green]test framework[/] to use")
      .PageSize(10)
      .MoreChoicesText("[grey](Move up and down to reveal more frameworks)[/]")
      .AddChoices(new[] {
          "XUnit", "NUnit","MSTest"
      }));
```

Let's add the above code to our project, i.e the *Program.cs*:

```csharp
using Spectre.Console;

Console.WriteLine("");
var answerReadme = AnsiConsole.Confirm("Generate a [green]README[/] file?");
var answerGitIgnore = AnsiConsole.Confirm("Generate a [yellow].gitignore[/] file?");

var framework = AnsiConsole.Prompt(
    new SelectionPrompt<string>()
        .Title("Select [green]test framework[/] to use")
        .PageSize(10)
        .MoreChoicesText("[grey](Move up and down to reveal more frameworks)[/]")
        .AddChoices(new[] {
            "XUnit", "NUnit","MSTest"
        }));
```

Running the program with `dotnet run`, you should see something like:

![prompt selection list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5tlg5nfb36yg5w3j3iyp.png)

### Adding a program Figlet

Let's add a figlet, a big header that make it feel like a real app. 

1. Just below the using directives, add this code:

   ```csharp
   AnsiConsole.Write(
    new FigletText("Scaffold-demo")
        .LeftAligned()
        .Color(Color.Red));
   ```

   Your full code so far should look like so:

   ```csharp
   using Spectre.Console;
 
   AnsiConsole.Write(
    new FigletText("Scaffold-demo")
        .LeftAligned()
        .Color(Color.Red));

    Console.WriteLine("");
    var answerReadme = AnsiConsole.Confirm(
     "Generate a [green]README[/] file?");
    var answerGitIgnore = AnsiConsole.Confirm(
     "Generate a [yellow].gitignore[/] file?");
   ``` 

   Try running it with `dotnet run`:

   ![Figlet added](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tc90yyz1oen23v9j4h12.png)

   Alright, now that's better, it almost like something we can be proud of :)
 

### Processing user input with `Status()`

Next, we want to add code to show how we are processing the user input, for that we can use `Status()`. It will show as a spinner and yuu can add text next to the spinner, real fancy I promise :).

`Status()` roughly works like so:

```csharp
AnsiConsole.Status()
    .Start("Generating project...", ctx =>
    {
      // define tasks to perform
      // define spinner type
      // define spinner color
      // define delay between tasks
    })
```

I've written some code comments in the above code to say what we need to do. We need to add a spinner that conveys to the user that tasks are being performed. Also, the user is used to things not being instantaneous, so it's a good idea to add a slight delay between the tasks.

Now, let's keep working on our project.

1. Add the following code at the bottom:

   ```csharp
   AnsiConsole.Status()
    .Start("Generating project...", ctx =>
    {
      if(answerReadme) 
      {
        AnsiConsole.MarkupLine("LOG: Creating README ...");
        Thread.Sleep(1000);
        // Update the status and spinner
        ctx.Status("Next task");
        ctx.Spinner(Spinner.Known.Star);
        ctx.SpinnerStyle(Style.Parse("green"));
      }

      if(answerGitIgnore) 
      {
        AnsiConsole.MarkupLine("LOG: Creating .gitignore ...");
        Thread.Sleep(1000);
        // Update the status and spinner
        ctx.Status("Next task");
        ctx.Spinner(Spinner.Known.Star);
        ctx.SpinnerStyle(Style.Parse("green"));
      }
      
      // Simulate some work
      AnsiConsole.MarkupLine("LOG: Configuring test framework...");
      Thread.Sleep(2000);
    });
   ```

Let's zoom in a on a piece of code:

```csharp
if(answerReadme) 
{
  AnsiConsole.MarkupLine("LOG: Creating README ...");
  Thread.Sleep(1000);
  // Update the status and spinner
  ctx.Status("Next task");
  ctx.Spinner(Spinner.Known.Star);
  ctx.SpinnerStyle(Style.Parse("green"));
}
```

- `AnsiConsole.MarkupLine()`, this types a log message at the start of the task
- `Thread.Sleep(1000);`, this ensures there's a pause between tasks
- `ctx.Status("Next task");`, this is the text shown next to the spinner
- The following code defines spinner type and spinner color:

   ```csharp
   ctx.Spinner(Spinner.Known.Star);
   ctx.SpinnerStyle(Style.Parse("green"));
   ```

Ok, your code should now look like so:

```csharp
using Spectre.Console;

AnsiConsole.Write(
new FigletText("Scaffold-demo")
    .LeftAligned()
    .Color(Color.Red));

Console.WriteLine("");
var answerReadme = AnsiConsole.Confirm("Generate a [green]README[/] file?");
var answerGitIgnore = AnsiConsole.Confirm("Generate a [yellow].gitignore[/] file?");

Console.WriteLine("");
var answerReadme = AnsiConsole.Confirm("Generate a [green]README[/] file?");
var answerGitIgnore = AnsiConsole.Confirm("Generate a [yellow].gitignore[/] file?");

var framework = AnsiConsole.Prompt(
  new SelectionPrompt<string>()
      .Title("Select [green]test framework[/] to use")
      .PageSize(10)
      .MoreChoicesText("[grey](Move up and down to reveal more frameworks)[/]")
      .AddChoices(new[] {
          "XUnit", "NUnit","MSTest"
      }));

AnsiConsole.Status()
  .Start("Generating project...", ctx =>
  {
    if(answerReadme) 
    {
      AnsiConsole.MarkupLine("LOG: Creating README ...");
      Thread.Sleep(1000);
      // Update the status and spinner
      ctx.Status("Next task");
      ctx.Spinner(Spinner.Known.Star);
      ctx.SpinnerStyle(Style.Parse("green"));
    }

    if(answerGitIgnore) 
    {
      AnsiConsole.MarkupLine("LOG: Creating .gitignore ...");
      Thread.Sleep(1000);
      // Update the status and spinner
      ctx.Status("Next task");
      ctx.Spinner(Spinner.Known.Star);
      ctx.SpinnerStyle(Style.Parse("green"));
    }
    
    // Simulate some work
    AnsiConsole.MarkupLine("LOG: Configuring test framework...")
;
    Thread.Sleep(2000);
  });
```

Let's take it for a spin with `dotnet run`. You should see something like:

![demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/es6l5nv7jj4ypcua2l6a.png)

## Exercise - install globally, run everywhere

Today you might consume apps from NuGet. However, some apps might not be something you want to share as it might be company internal, or they are personal projects. Is there a way to install things locally? In short, yes. We can pack up it up as NuGet package and install globally on our machine, this will make it available anywhere on your machine.

To make install locally, we need to perform the following steps:

- Tell our project file it needs to be packed as a tool
- Pack the program into a tool
- Install globally on our machine
- Run the app from anywhere :)

### Configure project file

We need to tell our project it's a tool. 

1. Open *spectre-demo.csproj* and locate `<PropertyGroup>` and add the following elements:

   ```xml
   <PackAsTool>true</PackAsTool>
   <ToolCommandName>spectre-demo</ToolCommandName>
   <PackageOutputPath>./nupkg</PackageOutputPath>
   ```

   these elements will identify our app as a tool, give it a name "spectre-demo" and define an output path where the package will end up

1. In a terminal, run `dotnet pack` to create a package:

   ```bash
   dotnet pack
   ```

1. Install globally by running below command:

   ```bash
   dotnet tool install --global --add-source ./nupkg spectre-demo
   ```

And we've done it! Let's test it out by navigating to any directory on your machine and run `spectre-demo`. That should start your app. Congrats!

I hope you liked this tutorial. Please comment if you want to see a part 2. Thanks for reading :)

## Summary

We discussed console apps and why we need them.

You were then introduced to Spectre.Console, a very competent NuGet package giving us all kinds of capabilities from graphical widgets to command line processing.

You also learned how to use some of these widgets and install your program as a global command.

Here's a [repo](https://github.com/softchris/spectre-demo)