Create your own .NET CLI tool

As a ASP.NET Core developer you are familiar with .NET Command Line Interface (CLI). You must have also used CLI tools such as dotnet-ef while building ASP.NET Core web apps. In addition to using built-in CLI tools you can create your own tools that can be used with .NET CLI. To this end this article introduces you to the overall tool creation and registration process.

A .NET CLI tool is essentially a console application that is wrapped in a NuGet package. You can then install this custom tool globally (machine level) or locally (project level) depending on your requirement. A .NET CLI tool typically accepts some input from the command prompt in the form of command line arguments and command line switches. You need to process this input in your console application as per your requirement. For example, a CLI tool might accept a database connection string as an input and create a set of tables in the database. The command line switches supply various options to the tool. For example, you might have a switch that tells the tool whether to overwrite the database tables if already present or use the existing ones.

Now that you have some basic idea about .NET CLI tool let's proceed to creating a simple tool. Out CLI tool will exist as MyDotNetTool.exe and it simply converts temperature value between Celsius and Fahrenheit. Once developed we will test it by installing it globally as well as locally.

 Begin by creating a new C# console app named MyDotnetTool using Visual Studio.

I am using .NET 6.0 but the same procedure should work for .NET 3.x and 5.x.

Then add System.CommandLine NuGet package to the project. 

This package allows you to easily handle command line arguments and options. Note that this package is still in beta and you might grab its latest version from the NuGet store.

Before we write the application logic into the console app, let's see how we are going to use it from command line. This will make you clear about the argument and options required by the application. Take a look at the following sample commands that show how MyDotNetTool.exe will be used.

> MyDotnetTool  100  --unit C
> MyDotNetTool  123.45  --u F

As you can see, MyDotnetTool.exe receives a temperature value as its input (100 and 1234.45 in this case). This is the argument supplied to the app. The --unit switch indicates the unit of measurement (C and F in this example) for the supplied argument. This is tool's option and also has an alias of --u.

Now that you have understood the usage of MyDotNetTool, let's write the app logic.

Open Program.cs and write the following code inside the Main() method.

static int Main(string[] args)
{

    var temperature = new Argument<double>
("temperature", "Temperature value");

    var unit = new Option<string>
("--unit", "Unit of measurement");
    unit.IsRequired = true;
    unit.AddAlias("--u");

    var cmd = new RootCommand();
    cmd.AddArgument(temperature);
    cmd.AddOption(unit);

    cmd.Handler = CommandHandler.Create
<double, string>(ShowOutput);

    return cmd.Invoke(args);
}

The code uses classes provided by System.CommandLine to parse and handle the command line arguments and options.

It begins by creating an Argument<T> that represents the temperature value argument. We specify the data type to be double. The first parameter to the Argument constructor is the name of the argument and the second parameter is its description. This information is used to show the command usage as you will see once we complete the tool.

Then we create an Option<T> object that represents a command line switch. In this case --unit is the option and also has an alias --u as indicated by the AddAlias() method. The IsRequired property indicates that --unit switch must be specified while invoking the tool.

Next, we create RootCommand object and add the Argument and Option to it.

We then wire a handler function called ShowOutput() for the command. Finally, Invoke() executes the handler function.

The ShowOutput() function accepts two parameters - double and string corresponding to the argument and option. ShowOutput() is shown below:

static void ShowOutput(double temperature, 
string unit="c")
{
    // C to F
    if(unit =="C")
    {
        temperature = (temperature * 9) / 5 + 32;
        unit = "F";
    }

    // F to C
    if(unit =="F")
    {
        temperature = (temperature - 32) *5 / 9;
        unit = "C";
    }

    Console.WriteLine($"Result : 
{temperature} \x00B0{unit}");
}

The ShowOutput() is quite simple and straightforward. It simply converts temperature values between Celsius and Fahrenheit using the necessary formulas. The final result after conversion is outputted on the console.

This completes the Main() and ShowOutput() methods. Although, MyDotNetTool is still to be prepared to be used as a CLI tool, you can run it for the sake of testing the temperature conversion logic. To do so, build the project and go to /Bin/Debug folder using Command Prompt. Then issue the following command:

> MyDotNetTool.exe  100  --unit C

If all goes well, you should get this output:

Result : 212 �F

You can also execute the app using dotnet run command as follows (make sure to navigate to project's root folder before invoking this command):

> dotnet run -- 100 --unit C

And you will get identical output.

So far so good. Although MyDotNetTool works as expected, it is still not a .NET CLI tool. You are yet to wrap it in a NuGet package. Let's do that now.

Open the project file (.csproj) and add this entry to it:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <PackAsTool>true</PackAsTool>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.CommandLine" 
Version="2.0.0-beta1.21308.1" />
  </ItemGroup>

</Project>

As you can see, we added <PackAsTool> element and set its value to true.

After making this change click on the Build > Pack menu option to generate a .nupkg file.

The above figure shows project name to be DotNetToolDemo. But if you are following the earlier instructions, your project name will be MyDotNetTool.

You will notice that a .nupkg file has been created in \Bin\Debug folder (Your file name will be MyDotNetTool.1.0.0.nupkg).

Next, we will register this tool globally so that you can run it from anywhere on the machine.

Just to that our tool is not currently installed on the machine, run this command:

> dotnet tool list --global

You should get a list of installed tools. For example, see the following output:

Package Id Version Commands
-------------------------------------------
dotnet-ef 6.0.0-preview.4.21253.1 dotnet-ef

As you can see, our tool isn't listed there.

Now install the tool globally using this command:

> dotnet tool install --global 
--add-source <project_root_path>/bin/debug MyDotNetTool

Here, we used dotnet tool command to install our own tool. The --global switch indicates that the tool is to be installed globally so that it can be invoked from anywhere on the machine. Since our .nupkg file is not a part of NuGet store, we specified --add-source switch to indicate the path of the .nupkg file. Then we supply tool name MyDotNetTool.

If all goes well, you will get this success message:

You can invoke the tool using the following command: MyDotNetTool
Tool 'mydotnettool' (version '1.0.0') was successfully installed.

To confirm the tool installation, run the list command again. This time you will see an entry for our tool.

Package Id          Version                      Commands
---------------------------------------------------------------
dotnet-ef           6.0.0-preview.4.21253.1      dotnet-ef
mydotnettool        1.0.0                        MyDotNetTool

To test the tool installation, go to some arbitrary folder on your machine and issue this command:

> MyDotNetTool  100  --unit C 

And you should get the correct output.

Now try issuing the command without specifying any argument and switch.

>MyDotNetTool

You will automatically get an output indicating the command usage as shown below:

Option '--unit' is required.
Required argument missing for command: MyDotNetTool

MyDotNetTool

Usage:
  MyDotNetTool [options] <temperature>

Arguments:
  <temperature>  Temperature value

Options:
  --u, --unit <unit> (REQUIRED)  Unit of measurement
  --version                      Show version information
  -?, -h, --help                 Show help and usage information

You can remove the globally installed tool using the following command:

> dotnet tool uninstall --global MyDotNetTool

In the preceding example you installed the tool globally so that it can be invoked from anywhere. You can also install a tool local to a folder. To do that you need to create a tool manifest (dotnet-tools.json) first.

Go into a folder from where you would like to invoke the tool. Then issue this command:

dotnet new tool-manifest

This will create tool manifest file dotnet-tools.json in the .config subfolder under the main folder.

Then invoke this command:

> dotnet tool install 
--add-source <project_root_path>/bin/debug MyDotNetTool

It is essentially the same command you used during global registration sans --global switch.

Now the tool is installed local to the folder and you can invoke it as follows:

> dotnet MyDotNetTool  100  --unit C

You can read about .NET tool creation in the official documentation here.

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant, trainer, author, and meditation teacher. He has been programming, meditating, and teaching for 25+ years. He conducts instructor-led online training courses in ASP.NET family of technologies for individuals and small groups. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Ajapa Yoga to interested individuals. To know more about him click here.

Get connected : Facebook  Twitter  LinkedIn  YouTube

Posted On : 05 July 2021


Tags : ASP.NET Core Data Access MVC .NET Framework C# Visual Studio