Click here to Skip to main content
15,887,135 members
Articles / Programming Languages / C#
Tip/Trick

A Tiny Little Library for Command-line Applications

Rate me:
Please Sign up or sign in to vote.
4.31/5 (3 votes)
10 Nov 2017CPOL4 min read 8.8K   3  
Uses Unity for DI and NLog for logging

Introduction

I am just sharing a small library I created for myself for creating command-line utilities in C#.

Every time I had to write some boilerplate code (I had created a template) to get basic logging and DI/IOC going on. Yes, even small utilities I have been writing for quite a while now use DI/IOC. And logging too.

It's available on GitHub as source (link to GitHub repository) and nuget.org as nuget package (GillSoft.ConsoleApplication).

Using the Code

To start using it, follow the steps given below:

  • Create C# console application targeting 4.6.2 minimum
  • Add nuget package "GillSoft.ConsoleApplication" to the project
  • In the Program.cs file put the code appropriatelyand run it:
C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GillSoft.ConsoleApplication.Demo
{
    static partial class Program
    {
        static void Main(string[] args)
        {
            var app = ApplicationFactory.Create();
            app.RegisterType<IResultHandler, ResultHandler>();
            app.RegisterType<ICalculator, SimpleInterestCalculator>();

            app.Run<ICalculator>(w => w.Calculate(300, 4 ,5));

        }
    }

    public interface IResultHandler
    {
        void Handle(decimal value);
    }

    class ResultHandler : IResultHandler
    {
        public void Handle(decimal value)
        {
            Console.WriteLine("Result is " + value);
        }
    }

    public interface ICalculator
    {
        void Calculate(decimal p, decimal t, decimal r);
    }

    class SimpleInterestCalculator : ICalculator
    {
        private readonly IResultHandler resultHandler;

        public SimpleInterestCalculator(IResultHandler resultHandler)
        {
            this.resultHandler = resultHandler;
        }

        public void Calculate(decimal p, decimal t, decimal r)
        {
            var si = p * t * r / 100;
            resultHandler.Handle(si);
        }
    }
}

In the above example, the class SimpleInterestCalculator calculates the simple interest based on passed values and needs some result handler to handle the final result. To keep things decoupled, it uses an instance of any class that implementes IResultHandler (in this case ResultHandler). In the beginning of the application, the types are registered with the application (actually the DI container contained in the instance of the application class).

The line "app.Run<ICalculator>(w => w.Calculate(300, 4 ,5))" leads to instatiation of the class SimpleInterestCalculator along with any depeendencies (ResultHandler, by virtue of its dependency on IResultHandfler) and the lambda expression calls the method "Calculate".

In the above examnple you notice the following:

  • Calculator and result handler are decoupled
  • When SimpleInterestCalculator class is instantiated, an instance of ResultHandler is passed to its constructor. The DI framework takes care of it! 

Now press RETURN key to close the console window. This is the starting point. Now we go into more details of the library. If you go to command-prompt and run the application with "-h" parameter, you will see it displays some help also.

-------------------------------[ Help ]--------------------------------
        Name        |                       Help
--------------------+--------------------------------------------------
h                   |Show help
v                   |Show detailed messages
vv                  |Show even more detailed messages
--------------------+--------------------------------------------------

You can use the in-built logging also in the above example. Replace the code of  SimpleInterestCalculator class with following:

class SimpleInterestCalculator : ICalculator
    {
        private readonly IResultHandler resultHandler;
        private readonly ILogger logger;

        public SimpleInterestCalculator(IResultHandler resultHandler,
            ILogger logger)
        {
            this.resultHandler = resultHandler;
            this.logger = logger;
        }

        public void Calculate(decimal p, decimal t, decimal r)
        {
            logger.Debug("P: " + p);
            logger.Debug("T: " + t);
            logger.Debug("R: " + r);
            var si = p * t * r / 100;
            logger.Info("SI: " + si);
            resultHandler.Handle(si);
        }
    }

Now if you run this code from command-line you will see following:

Result is 60

But if you run the same application with "-vv", you see following output:

2017-11-10 22:40:32 DEBUG P: 300
2017-11-10 22:40:32 DEBUG T: 4
2017-11-10 22:40:32 DEBUG R: 5
2017-11-10 22:40:32 INFO SI: 60
Result is 60

You will notice that "-v" leads to logging of "INFO" level messages and "-vv" leads to "DEBUG" level messages.

The library has some classes and interfaces that I will explain as I go along.

ApplicationFactory

Creates an instance of the in-built class that implements IApplication. The entry point to the application class is the Run method. It takes a delegate/lambda expression which accepts two parameters of type ILogger and IApplication. Within the callback, you can implement your own code and use the logging and other features of the application.

IApplication

This interface is implemented by an in-built class and provides functionality of registering stuff with the DI container. Ah, I forgot to mention that the library uses Unity for DI and NLog for logging.

ICommandlineParamters

This interface is implemented by a class that handles three command-line parameters -h for help, -v for logging INFO level messages and -vv for logging DEBUG level messages.

CommandlineParamtersBase

If you need to have more command-line parameters, create a class and inherit from this class. Also define an interface for that class so that it can be used to register with the DI container. See the example application on GitHub.

IApplicationConfiguration

Implemented by an in-built class that provides basic functionality of reading values from Apo.config. if you need a more specialized class, follow what you read about custom command-line parameters class.

ILogger

This interface is implemented by a class in the library which uses NLog for all logging purposes. By default, all log messages appear on console. If file called “NLog.config” is present in the directory of the application, it will be used for defining the logging behaviour.

Needless to say, you can implement your own logging class with ILogging interface and register it with the application.

Extra Bits

Some utility code is also provided. Most of the times, I have to present data in tabular format on the screen. So, here comes ITableFormatter and its implementation. An extension method AsTableFormatter which can be applied to all IEnumerable<T> instances returns an instance of TableFormatter which can be used to define columns and then print. An example to use that method is shown below:

C#
var persons = new[]
{
    new{ FirstName = "John", LastName = "Smith"},
    new{ FirstName = "Jane", LastName = "Doe"},
    new{ FirstName = "Vijay", LastName = "Gill"},
};

persons.AsTableFormatter(output)
    .Column("First Name", 30, a => a.FirstName)
    .Column("Surname", 30, a => a.LastName)
    .Print("Table of people using extension method",
    "This is header 1",
    "This is header 2"
);

This shows the following output:

-------------------------[ People ]--------------------------
* This is header 1
* This is header 2
------------------------------+------------------------------
          First Name          |           Surname
------------------------------+------------------------------
John                          |Smith
Jane                          |Doe
Vijay                         |Gill
------------------------------+------------------------------

IOutput

Lastly, I must mention this interface too. The library uses the implementation of this interface internally to display things in the console. The example also showed “Hello world” using it. The in-built implementation writes the text in console. But you may have your own class implementing this interface, but writing to a file. Simply register it with the application and all the output goes to the file as implemented by you!

Example Application on GitHub

The example application on GitHub can be followed to see almost all the features in one go. I could explain the example application here but then the content of this tip would have become too long.

Just pay attention to how the Worker class is registered in the example and then instantiated with all the dependencies injected. Try running application with "-v" and "-vv" parameters. And try "-h" too, to see help.

I hope you'll like this tiny little library and use it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) GillSoft Limited
Ireland Ireland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --