Click here to Skip to main content
15,887,416 members
Articles / Programming Languages / C# 4.0

Console Program Entry-point Framework

Rate me:
Please Sign up or sign in to vote.
3.22/5 (5 votes)
20 Sep 2009LGPL34 min read 23.9K   119   14   3
Write better console applications, quickly, and easily

Who Should Read This?

Anyone who writes console applications – even if only once!  

Prerequisites

A basic understanding of C# is a must, methods, arguments, and attributes. While you don't need to understand the code that comprises the framework – you'll need to understand how to use it. If you don't understand this much, then I'd be very curious why you were reading this article in the first place!

Introduction

During my development career, I have written numerous utilities – assistive tools to speed up repetitive tasks. My pattern for writing these tools is usually to write an assembly to encapsulate the tool logic, and another assembly for executing the logic – usually a console application. That way, I can use my utility either from the command line, or directly from within the product for which the utility has been written, without having to shell out to another process. 

However, it doesn't take long before one gets tired of the repetitive steps of writing console applications – parsing command line arguments, switches, type conversions, and informational output … rather tedious as you can imagine. Not to mention, it’s a task in itself to remain consistent in the design and output of these console applications – more often than not resulting in an eclectic collection of conventions.

So, to coin a phrase, it was time to abstract the process of abstraction. It turned out to be quite straightforward, resulting in a highly flexible framework that I would like to share with you.

The utility detailed in this document is geared around my particular requirements of console applications. The nature of command line invocation is inherently free form, but from observing the typical implementation across numerous tools I have written, a common usage convention emerged. It is upon that convention that I have designed this framework – which may not suit all applications – but if you conceive your utilities with this convention in mind, you may find the benefits far outweigh any restrictions it places upon you.

The commonalities across all console applications are:

  1. The application has one or more ‘actions’, each with its own set of customizations (arguments), and each a mutually exclusive entry-point to the application.
  2. The application must parse string arguments to CLR types for use in the program logic.
  3. The application’s input validation must generate meaningful error messages when invalid arguments are used, and before any domain specific logic is begun.
  4. The application must generate usage documentation, typically when the program is invoked with either no arguments, invalid arguments, or the ‘/?’ switch.
  5. The application must support default states for arguments, only requiring them to be specified when the desired value deviates from the default.

Typically, a single console application performs a single action per invocation; this action is configured by passing various arguments. If one applies this ‘program schema’ to their utility, the command-line convention is perfectly analogous to the well known ‘method + parameters’ program anatomy.

The console application actions are synonymous to program entry-points (methods), and the arguments are of course the method parameters. By using custom attributes, one can mark-up a program structure, and use a utility runtime to execute these methods, with all the required parameters parsed, and strongly typed, from the command line arguments.

The framework can infer all the necessary usage documentation and error messages from the program schema. This facilitates a standardised and consistent output format across all console programs – because it is generated by the framework, rather than being rewritten each time. The value gained cannot be denied, when used in a large development team, considering the difficulty in ensuring all developers follow strict conventions in structuring command-line usage and diagnostic output.

Before writing a single line of program logic, your console application will have already achieved a professional, and consistent, ‘user interface’.

The following image shows the output of the .NET Framework’s ‘gacutil’:

Image 1

The next one shows similar output from our test harness – this is all automatically generated by the framework.

Image 2

The assembly meta-data is gleaned for the introductory copyright statements, and the usage information from the mark-up attributes.

Getting Started 

The mark-up attributes reside in the Adlib.Console.Markup namespace, each with a localized equivalent in the Localized sub-namespace. The code example below demonstrates the usage of the localized version – the primary difference being that the Description attribute-property specifies a resource name, rather than the actual description value. This name is then used to locate the appropriate string resource from the resource-type specified by the ConsoleProgram attribute.

The Program class is marked with the ‘ConsoleProgram’ attribute, which provides the opportunity to define a default verb, which is essentially the method that will be invoked should no command-line arguments be specified.

Each method that needs to be accessible from the command line is decorated with the Verb attribute, where the ‘switch’ is specified, along with its description.

C#
[ConsoleProgram(typeof(Resources), DefaultVerb = "/?")]
class Program
{
    static void Main(string[] args)
    {
        typeof(Program).Execute(args);
        System.Console.ReadLine();
    }

    [Verb("/l", Description = "ListDescription")]
    public static void List(
        [Argument("/assembly_name", Description="ListAssemblyName")]
        string assemblyName)
    {
        System.Console.WriteLine(string.Format
	("Called the 'List' method with 'assembly_name' = {0}", assemblyName));
    }

    [Verb("/?", Description = "HelpDescription")]
    public static void Help()
    {
        typeof(Program).PrintHelp();
    }

Extensions methods provide access to the ‘runtime’. By simply calling ‘Execute’ on the Program type, and passing the arguments array, the framework takes care of locating the correct method to invoke, and parses all the arguments from strings to the correct parameter types.

Default values are implemented by specifying them in the Argument attribute. This is demonstrated in the following example:

C#
[Verb("/i", Description = "InstallDescription")]
public static void Install(
        [Argument("/assembly_path")]string assemblyPath, 
        [Argument("/f", Default=false)]bool force)
{
    System.Console.WriteLine(
        string.Format("Install: 'assembly_path' = {0}, 
		'force' = {1}.", assemblyPath, force));
} 

History

  • 2009.09.19: Initial publication

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer (Senior)
New Zealand New Zealand
"Find a job you love, and you'll never work a day in your life."

Adam Langley is a software engineer in Auckland, New Zealand.

Software development is his personal passion, he takes pride in his work, and likes to share his experiences with the development community.

When he's not coercing computers, you'll find him riding his motorcycle, or attempting to carve something creative from bone.

Comments and Discussions

 
QuestionWhere's the code? Pin
PIEBALDconsult19-Sep-09 13:07
mvePIEBALDconsult19-Sep-09 13:07 
AnswerRe: Where's the code? Pin
Adam Langley19-Sep-09 13:20
Adam Langley19-Sep-09 13:20 
GeneralRe: Where's the code? Pin
PIEBALDconsult19-Sep-09 16:17
mvePIEBALDconsult19-Sep-09 16:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.