Monday, October 27, 2008

A fast and easy console shell - Part 1

There are many times when I need to have human interaction with a service or some kind of business logic and do not want to go through the hassle of writing a GUI for it. In these cases I always use a console application. Console applications are easy to use and provide a quick and dirty way of getting things done quickly. However, if you have a lot of things that need to go into the application things can get ugly fast. When I say console applications, I’m talking about a console shell. An application that you start and has a command prompt where the user can type in a command to perform an action. Other types of console applications are created just for the purpose of performing an action by calling the .exe and passing command line parameters to it. A good example of a console shell application is NetSh.exe. As I said before things can get ugly fast. The other day I was working on just this type of solution where I did not need a user interface for the service I was writing just a way to monitor events or send the service commands. So, I started to write the console shell and it started to get bad—if statements, switch statements, all bad. I took a little time and focused on this (this is not something I would normally spend a lot of time thinking about because it’s just a tool) and came up with a good and fast way to implement a console shell and keep it clean.

1) The first step is to create a base command class:

public abstract class ConsoleCommand
{
public ConsoleCommand ( )
{

}

public void Execute( string parms )
{
ExecuteCore( parms );
}

protected abstract void ExecuteCore( string parms );
}

2) Create a class level attribute.

[AttributeUsage(AttributeTargets.Class)]
public class ConsoleCommandAttribute : Attribute
{
public ConsoleCommandAttribute( string commandName )
{
CommandName = commandName.ToUpper();
}

public string CommandName { get; set; }

}


3) Create a new console command.

[ConsoleCommandAttribute ("Run sample" )]
public class RunSampleCommand : ConsoleCommand
{
public RunSampleCommand ( )
{

}

protected override void ExecuteCore( string parms )
{
Console.WriteLine( "Command executed" );
}
}

4) The next step is to find and load all of the commands when the application starts up.

static void LoadCommands( )
{
Assembly ass = Assembly.GetEntryAssembly( );

var query = ( from t in ass.GetTypes( )
where
Attribute.IsDefined( t ,
typeof(ConsoleCommandAttribute) ,
false ) == true
select t ).ToList( );

foreach ( Type type in query )
{
ConsoleCommandAttribute att =
Attribute.GetCustomAttribute( type ,
typeof(ConsoleCommandAttribute) ,
false ) as ConsoleCommandAttribute;

// You could create the type here or wait until it is ask for. The latter is the better option.
ConsoleCommand cmd =
(ConsoleCommand)Activator.CreateInstance( type );

CommandList.Commands.Add( att.CommandName , cmd );
}
}
}

5) Next handle the command the user entered.

String cmd = Console.ReadLine();

cmd = cmd.ToUpper();

if ( CommandList.Commands.ContainsKey( cmd ) )
{
try
{
CommandList.Commands [ cmd ].Execute( string.Empty );
}
catch ( Exception ex )
{
Console.WriteLine( ex.Message );
}
}
else
{
Console.WriteLine( "Command not found." );
}
}
Once the command has finished, you will need to call back to the method that waits for the user to enter a new command.

In part two, I will talk about how to handle parameters without string parsing.