Creating a .NET Tool - Part 1: Feeding the Dragon
- 4 minutes read - 852 wordsI’ve been using .NET Tools more and more recently; once you have .NET on your system, they’re so easy to install with just one command line call and are immediately available on the path. But how does one actually create a tool and make it available? I thought I’d do a series on how to authour a tool and make it available for public installation.
Let’s jump right in!
Requirements
The tool I’m creating is a simple JSON validator: you point it to a JSON file on disk and it returns whether the file is valid JSON or not. You should also be able to allow comments with a toggle option. Simple!
Project setup
Tools are typically live in a console app, so let’s create one:
$ dotnet new console -o src/jsonv -n JsonValidate
Let’s now add a library to make parsing command line arguments a breeze:
$ cd src/jsonv
$ dotnet add package System.CommandLine.DragonFruit --version 0.3.0-alpha.20214.1
Seriously: if you haven’t seen or heard of
System.CommandLine
orSystem.CommandLine.DragonFruit
yet, go check it out. It’s a new command line parser aimed at unifying multiple unsuccessful previous efforts by various parties at creating command line parsers, bringing a really nice syntax and a helpful set of features. For instance, the DragonFruit variant allows you to supply aguments through the main method! See below.
Now our project is setup, we can configure the command line argument parsing.
Feeding the dragon
As mentioned, with DragonFruit, we can supply command line arguments via the main method. This really minimises the amount of boiler plate code required and works well for small parsers.
using System;
namespace JsonValidate
{
class Program
{
/// <param name="filePath">The absolute path to the JSON file</param>
/// <param name="allowComments">Option to allow comments in the file</param>
static void Main(string filePath, bool allowComments = false)
{
Console.WriteLine($"File path is: {filePath}, allowing comments: {allowComments}");
}
}
}
Here, I’ve added two arguments along with their XML documentation. DragonFruit will automatically parse the arguments into the main method and will also bundle the XML documentation into a super useful help
option. For instance, running the help command gives:
$ dotnet run -- -h
Usage:
jsonv [options]
Options:
--file-path <file-path> filePath
--allow-comments allowComments
--version Show version information
-?, -h, --help Show help and usage information
And running the program with some arguments:
$ dotnet run -- --file-path /src/jsonv/Program.cs --allow-comments
File path is: /src/jsonv/Program.cs, allowing comments: True
How good is that?! With a few lines of code, we already have a fully documented help command and command line parsing done. Let’s continue with the rest of the program.
Eating the fruit
Now the command line arguments are being parsed, we can add the code to validate the file. Let’s use System.Text.Json for this:
Seriously: if you haven’t seen or heard of
System.Text.Json
yet, go check it out. It’s a new JSON de/serialiser aimed at high performance and is now the default JSON serialiser in ASP.NET Core.
using System;
using System.IO;
using System.Text.Json;
namespace JsonValidate
{
class Program
{
/// <param name="filePath">The absolute path to the JSON file</param>
/// <param name="allowComments">Option to allow comments in the file</param>
static void Main(string filePath, bool allowComments = false)
{
if (!File.Exists(filePath))
{
Console.WriteLine("Error: file does not exist!");
return;
}
Console.WriteLine($"File path is: {filePath}, allowing comments: {allowComments}");
try
{
var serializerOptions = new JsonSerializerOptions
{
ReadCommentHandling = allowComments ? JsonCommentHandling.Skip : JsonCommentHandling.Disallow
};
JsonSerializer.Deserialize<object>(File.ReadAllBytes(filePath), serializerOptions);
Console.WriteLine("File is valid JSON!");
}
catch (Exception e)
{
Console.WriteLine("File is NOT valid JSON!");
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
Yes: this may not be the most efficient way to read the file but it’s just a demo.
Running this now on the project file gives:
$ dotnet run -- --file-path /src/jsonv/Program.cs
File path is: /src/jsonv/Program.cs, allowing comments: False
File is NOT valid JSON!
Error: '0xEF' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
Great, the file was not valid since it is not JSON and we get an error message.
Now, given this JSON file:
{
// This is a comment
"sdk": {
"version": "3.1.201"
}
}
We can run the following commands to check the JSON handling:
$ dotnet run -- --file-path /global.json
File path is: /global.json, allowing comments: False
File is NOT valid JSON!
Error: '/' is invalid after a value. Expected either ',', '}', or ']'. Path: $ | LineNumber: 1 | BytePositionInLine: 2.
Great, the file was not valid due to not allowing comments.
$ dotnet run -- --file-path /global.json --allow-comments
File path is: /global.json, allowing comments: True
File is valid JSON!
Great, the file was valid due to allowing comments.
Summary
In this post, we created a simple JSON validator console app with help from System.CommandLine.DragonFruit
. This library gets you from zero to complete command line app in minutes. I would really recommend giving it a go, and also it’s more stable sibling System.CommandLine
. In the next post, we’ll look at how to package up this console app as a .NET Tool and publish it to NuGet. Stay tuned!