.NET Core is sexy — Real-world Part 1: Command Line Application


Jeremy Buisson

This story directly follows my previous: .NET Core is sexy, and you should know it and bring a real-world application example. If you have not already read it and are not familiar with .NET Core, I encourage you to do so before continuing.

My intention is to create a curation application that we will improve step by step, ending with a full production-ready platform. We will start creating a simple command line application to begin, with an Sqlite database.

At this step, we are building a command line application that we will use to store items defined by a unique identifier, a name and a created date. We will then be able to display our current curation list and remove an item from it.

curator add medium.com/@jbuisson
curator add blog.cleancoder.com
curator list
> 1: medium.com/@jbuisson, 8/3/19 9:58:56 AM.
> 2: blog.cleancoder.com, 8/3/19 9:59:17 AM.
curator remove 1
curator list
> 2: blog.cleancoder.com, 8/3/19 9:59:17 AM.

Create a new console application:

dotnet new console -o Curator

We will use Microsoft.EntityFramework as our database object-relational mapping (ORM), configured for Sqlite. In addition, we will use CommandLineUtils — from Nate McMaster — an awesome package for easily writing command line applications.

From within our newly created project, run the following commands to add the packages:

dotnet add package McMaster.Extensions.CommandLineUtils 
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design

We now must create our Item model to enable saving it in an Sqlite database.

This kind of classes is commonly called a DTO, for Data Transfer Object.

We have a C# class with public properties to get/set the Id, Name and CreatedAt. We also override the ToString() method to display the Item information.

Next, we create the EntityFramework DbContext and configure it to use Sqlite.

The easiest way to declare a DbContext, but not the most useful one as we will see later.

Again, nothing is tricky here; the DbContext serves as the bridge for accessing data from the database. We declare a DbSet for the Item model, which indicates that we have an Items table in the database.

Additionally, we tell EntityFramework to UseSqlite, with the path to the Sqlite database file. This path is relative to the binary execution directory. Feel free to use any other path you see fit, but ensure the target directory exists; otherwise, you might encounter the following application error:

SQLite Error 14: ‘unable to open database file’.

Prior to setting up the application itself, we create a new EntityFramework migration and run it to set up the database.

dotnet ef migrations add InitialCreate
dotnet ef database update

You should see a curator.db file appears in the directory that you have previously set up in the DbContext. If you did not change the DataSource path, you should be able to find it at the root of the workspace.

Finally, we create our commands.

The code is explicit. We have a base CuratorCommand class, which works as a parent class for all the other commands and is responsible for the CuratorContext. In addition, we have three other classes for the following three commands: add, list, remove.

I encourage you to explore the commands. Consider even creating your own to familiarize yourself with the application and how it works.

The final step is to update our application’s main entry point. Doing so should result in something similar to the following:

The whole application in one file, not really sexy…

Now, it is time to test it.

dotnet run add medium.com/@jbuisson
dotnet run add blog.cleancoder.com
dotnet run list
> 1: medium.com/@jbuisson, 8/3/19 11:13:37 AM.
> 2: blog.cleancoder.com, 8/3/19 11:13:42 AM.
dotnet run remove 1
dotnet run list
> 2: blog.cleancoder.com, 8/3/19 11:13:42 AM.

We have a working command line application, but it is poorly designed and does not respect any clean code principle.

I am aiming for this project architecture, splitting the console application into multiple class libraries:


├── data
├── src
├── Curator.Console
├── Curator.Data
├── Curator.Data.Entities
├── Curator.Data.EntityFramework
├── Curator.Data.EntityFramework.Context
├── Curator.Data.EntityFramework.Sqlite
├── tests

This might look somewhat complicated — that is, having such depth and splitting Entities, Context and Sqlite into three different libraries. However, multiple reasons support using this method:

  • I should be able to use Entities without EntityFramework; they are two concerns with their own responsibilities and so forth should be separated. This is not a dogmatic vision but a pragmatic one. We could remove EntityFramework at some point to use another ORM — or none — and doing so would not be too costly thanks to this approach.
  • EntityFramework allows us to use a wide range of databases. This means that while using the same Context, we could use different databases. Spoiler: we will do this both when testing and porting the application to the web stack.

The data folder simply serves as our Sqlite repository. The tests folder is self-explanatory and will be used once the refactoring has been completed.

I will also include some runtime checks for the sake of null or invalid arguments.

I cannot consciously consider this step without adding some tests. Once more, you will see that it is simple to set up tests based on your code and then run them.

To test our command, we do not want to use a real Sqlite database. EntityFramework has an InMemory provider to help us do so.

cd src/Curator.Data/Curator.Data.EntityFramework
dotnet new classlib -o Curator.Data.EntityFramework.Memory
cd Curator.Data.EntityFramework.Memory
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add reference ../Curator.Data.EntityFramework/Curator.Data.EntityFramework.Context/

We now only have to create a new DesignTimeDbContextFactory and set it up with UseInMemoryDatabase. A trick here is to add a unique database name on each new DbContext to isolate each test. For that I use DateTime.Now.Ticks, which is sufficiently safe, but you could instead use Guid.NewGuid() or any unique random generator.

Once this has been completed, we should go to the tests folder and create a new test project.

cd tests
dotnet new xunit -o Curator.Console.Tests

We must add references to the project that we wish to test in addition to the new database provider that we have just created:

dotnet add reference ../../src/Curator.Console
dotnet add reference ....srcCurator.DataCurator.Data.EntityFrameworkCurator.Data.EntityFramework.MemoryCurator.Data.EntityFramework.Memory.csproj

You can now write a simple test for the AddCommand:

Finally, once all tests have been written, we have a clean command line application using .NET Core, compatible with Linux, Windows and Mac! From my previous story you should now be able to release this application easily:

dotnet publish src/Curator.Console -c Release — self-contained true -r [YOUR_RUNTIME_IDENTIFIER] -o [INSTALLATION_PATH]

This is it for our first step. Next one will be the web API deployed on a server or using Docker.