MadProps.AppArgs

Something I find myself doing a bit, particlarly in business applications, is parsing command-line arguments. For example, if you have one application that could be run against different servers or databases, you can set up a shortcut like this:

myapp.exe /server=sqlserver1 /database=mydb

The application can then configure its database connection at runtime. This is a contrived example but you get the idea.

Typically, command-line parsing code is quite messy. It's lots of foreach loops and string matching. And then you have to worry about keeping your help text (displayed if the command line is missing a required argument, for example) up to date.

So I've had a crack at a simplified way to parse command line arguments, and I've called it MadProps.AppArgs.

It's pretty easy to use. First, you define a simple POCO class to represent all the arguments your application might take. Like this:

public class MyArgs
{
    public string Server { get; set; }
    public string Database { get; set; }
}

Now in your application's entry point, you can add a using MadProps.AppArgs statement, and you'll have an extension method you can use like this:

class Program
{
    static void Main(string[] args)
    {
        var myArgs = args.As<MyArgs>();
    }
}

That's all you need to do! Now run the application using the command line above, and you'll have an instance of MyArgs with its Server property set to "sqlserver1" and its Database property set to "mydb"!

Short Names

Let's assume you don't want the argument named "/database" but instead want it named "/db". You could rename the property on your class, or you could use the System.ComponentModel.DataAnnotations.Display attribute to give it a short name:

public class MyArgs
{
    public string Server { get; set; }

    [Display(ShortName = "db")]
    public string Database { get; set; }
}

Now you can run the app like this for the same result:

myapp.exe /server=sqlserver1 /db=mydb

Required Arguments

Now, you might decide that the Database argument is required, so you can add the System.ComponentModel.DataAnnotations.Required attribute:

public class MyArgs
{
    public string Server { get; set; }

    [Required]
    [Display(ShortName = "db")]
    public string Database { get; set; }
}

If we leave the database argument off the command line, our call to As<MyArgs>() will throw an ArgumentException. We might want to catch that and show the user how to run the app. Let's do that.

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var myArgs = args.As<MyArgs>();
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine();
            Console.WriteLine(AppArgs.HelpFor<MyArgs>());
        }
    }
}

Now if we run the app and omit the /db argument, we see this output:

Argument missing: 'db'

ConsoleApplication10 /db=value [/server=value]

 /db
 /server

Argument Descriptions

The output above kind of helpful in that it has told us which arguments are required and which are optional, but it'd be nicer if the arguments had descriptions. Let's do that with the System.ComponentModel.Description attribute, and/or with the Description property of the Display attribute:

public class MyArgs
{
    [Description("The database server to connect to")]
    public string Server { get; set; }

    [Required]
    [Display(ShortName = "db", Description="The database to use")]
    public string Database { get; set; }
}

Let's run it again without the "/db" argument:

Argument missing: 'db'

ConsoleApplication10 /db=value [/server=value]

 /db            The database to use
 /server        The database server to connect to

That's better!

Switches

What if we want a parameter that tells the application to run as an admin, but doesn't have a value? These are sometimes called "command-line switches". So we can run our app like this:

myapp.exe /server=sqlserver1 /db=mydb /admin

That's as easy as adding a boolean property to our class, like this:

public class MyArgs
{
    [Description("The database server to connect to")]
    public string Server { get; set; }

    [Required]
    [Display(ShortName = "db", Description = "The database to use")]
    public string Database { get; set; }

    [Description("Runs the program as an administrator")]
    public bool Admin { get; set; }
}

Let's see what happens to our output now if I omit the "/db" parameter again:

Argument missing: 'db'

ConsoleApplication10 /db=value [/admin] [/server=value]

 /admin         Runs the program as an administrator
 /db            The database to use
 /server        The database server to connect to

So the new "/admin" parameter has been added to the documentation, and you can see in the example command line that it does not require a value.

Other Argument Types

Let's add another property with a different type. Let's say we want to pass a "timeout" parameter of type int. We'll add the property:

public class MyArgs
{
    [Description("The database server to connect to")]
    public string Server { get; set; }

    [Required]
    [Display(ShortName = "db", Description = "The database to use")]
    public string Database { get; set; }

    [Description("Runs the program as an administrator")]
    public bool Admin { get; set; }

    [Description("Timeout, in seconds, for commands")]
    public int Timeout { get; set; }
}

Now we can pass "/timeout=100" on the command line and the property will receive the value. If we pass "/timeout=foo", which is not a valid integer, we'll get a type conversion exception that we'll also need to catch.

Shortcuts

MadProps.AppArgs will do a PowerShell-like trick of only requiring enough characters in an argument to uniquely identify it, so we could also run our application like this:

myapp.exe /s=sqlserver1 /d=mydb /a /t=100

Where do I get it?

You can check out MadProps.AppArgs on Bitbucket. I don't have a binary download, but feel free to pull down the source and have a play. There are features I haven't listed here, and maybe more to come!

.net c#
Posted by: Matt Hamilton
Last revised: 28 Mar, 2024 10:11 AM History

Trackbacks

Comments

26 May, 2011 05:40 PM

Sweet! But you've got to get this up in Nuget. It'll make so much easier for us to play with!

As Rob Connery put it:

You know - it’s to the point where if it’s not in NuGet, it’s dead to me.

26 May, 2011 06:30 PM

This is really clever! I also vote for putting this on Nuget.

Peter
Peter
26 May, 2011 07:30 PM

Really nice! I was going to suggest adding array support, but after checking out the code it looks like you read my mind ...

One more idea: how about adding support for default values (using System.ComponentModel.DefaultValueAttribute)?

Finally, here's another vote for adding it to NuGet. Maybe you could add it as a loose source file - so that when I install it I just get a copy of AppArgs.cs in the root of my project.

Jon Simpson
Jon Simpson
26 May, 2011 08:23 PM

Very cool - nicely done! I will pile on for the NuGet package.

26 May, 2011 08:48 PM

Peter's idea of using NuGet to deliver just the loose source file is not a bad idea given how small it is (though perhaps creating a MadProps folder to put it in would be better then dumping it in the root).

Daniel Cazzulino discusses how to do it if you've not done it before (it's easy).

26 May, 2011 10:12 PM

Lots of good feedback so far! Thanks guys!

So people don't mind the idea of a loose file Nuget package? I had considered Nuget (this would be my first package) but hadn't thought about the loose file idea. I suppose that would work for .NET 3.5SP1 as well as .NET 4 projects, which is a bonus.

@Peter re: DefaultValueAttribute. I did consider that, but then I thought, "hey, this is just a POCO! If I want one of my properties to have a default value, I'll just add a constructor and set it there!" What do you think?

Peter
Peter
26 May, 2011 10:29 PM

Hi Matt -

I had the same thought right after posting my comment. Assigning default values in the constructor makes sense.

Great work!

No new comments are allowed on this post.