├── ConsoleApplicationBase
├── App.config
├── ConsoleFormatting.cs
├── Commands
│ ├── Users.cs
│ └── DefaultCommands.cs
├── Properties
│ └── AssemblyInfo.cs
├── Models
│ └── SampleData.cs
├── ConsoleCommand.cs
├── ConsoleApplicationBase.csproj
└── Program.cs
├── .gitignore
├── ConsoleApplicationBase.sln
└── README.md
/ConsoleApplicationBase/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Thumbs.db
2 | *.obj
3 | *.exe
4 | *.pdb
5 | *.user
6 | *.aps
7 | *.pch
8 | *.vspscc
9 | *_i.c
10 | *_p.c
11 | *.ncb
12 | *.suo
13 | *.sln.docstates
14 | *.tlb
15 | *.tlh
16 | *.bak
17 | *.cache
18 | *.ilk
19 | *.log
20 | [Bb]in
21 | [Dd]ebug*/
22 | *.lib
23 | *.sbr
24 | obj/
25 | [Rr]elease*/
26 | _ReSharper*/
27 | [Tt]est[Rr]esult*
28 | *.vssscc
29 | $tf*/
--------------------------------------------------------------------------------
/ConsoleApplicationBase/ConsoleFormatting.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | // code was found at the C# Examples Site: http://www.csharp-examples.net/indent-string-with-spaces/
8 |
9 | namespace ConsoleApplicationBase
10 | {
11 | public class ConsoleFormatting
12 | {
13 | public static string Indent(int count)
14 | {
15 | return "".PadLeft(count);
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ConsoleApplicationBase.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.30723.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApplicationBase", "ConsoleApplicationBase\ConsoleApplicationBase.csproj", "{D21CC334-9E7D-4A29-B6F9-5120351EC703}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {D21CC334-9E7D-4A29-B6F9-5120351EC703}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {D21CC334-9E7D-4A29-B6F9-5120351EC703}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {D21CC334-9E7D-4A29-B6F9-5120351EC703}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {D21CC334-9E7D-4A29-B6F9-5120351EC703}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/ConsoleApplicationBase/Commands/Users.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using ConsoleApplicationBase.Models;
7 |
8 | namespace ConsoleApplicationBase.Commands
9 | {
10 | public static class Users
11 | {
12 | public static string Create(string firstName, string lastName)
13 | {
14 | Nullable maxId = (from u in SampleData.Users
15 | select u.Id).Max();
16 | int newId = 0;
17 | if(maxId.HasValue)
18 | {
19 | newId = maxId.Value + 1;
20 | }
21 |
22 | var newUser = new User { Id = newId, FirstName = firstName, LastName = lastName };
23 | SampleData.Users.Add(newUser);
24 | return "";
25 | }
26 |
27 |
28 | public static string Get()
29 | {
30 | var sb = new StringBuilder();
31 | foreach(var user in SampleData.Users)
32 | {
33 | sb.AppendLine(ConsoleFormatting.Indent(2) + string.Format("Id:{0} {1} {2}", user.Id, user.FirstName, user.LastName));
34 | }
35 | return sb.ToString();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ConsoleApplicationBase/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("ConsoleApplicationBase")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ConsoleApplicationBase")]
13 | [assembly: AssemblyCopyright("Copyright © 2014")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("b776aaea-d3a1-435e-b7cf-623792acc812")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/ConsoleApplicationBase/Models/SampleData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ConsoleApplicationBase.Models
8 | {
9 | // We'll use this for our examples:
10 | public class User
11 | {
12 | public int Id { get; set; }
13 | public string FirstName { get; set; }
14 | public string LastName { get; set; }
15 | }
16 |
17 |
18 | public static class SampleData
19 | {
20 | private static List _userData;
21 | public static List Users
22 | {
23 | get
24 | {
25 | // List will be initialized with data the first time the
26 | // static property is accessed:
27 | if(_userData == null)
28 | {
29 | _userData = CreateInitialUsers();
30 | }
31 | return _userData;
32 | }
33 | }
34 |
35 |
36 | // Some test data:
37 | static List CreateInitialUsers()
38 | {
39 | var initialUsers = new List()
40 | {
41 | new User { Id = 1, FirstName = "John", LastName = "Lennon" },
42 | new User { Id = 2, FirstName = "Paul", LastName = "McCartney" },
43 | new User { Id = 3, FirstName = "George", LastName = "Harrison" },
44 | new User { Id = 4, FirstName = "Ringo", LastName = "Starr" },
45 | };
46 | return initialUsers;
47 |
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/ConsoleApplicationBase/Commands/DefaultCommands.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | // All console commands must be in the sub-namespace Commands:
8 | namespace ConsoleApplicationBase.Commands
9 | {
10 | // Must be a public static class:
11 | public static class DefaultCommands
12 | {
13 | // Methods used as console commands must be public and must return a string
14 |
15 | public static string DoSomething(int id, string data)
16 | {
17 | return string.Format(ConsoleFormatting.Indent(2) +
18 | "I did something to the record Id {0} and saved the data '{1}'", id, data);
19 | }
20 |
21 |
22 | public static string DoSomethingElse(DateTime date)
23 | {
24 | return string.Format(ConsoleFormatting.Indent(2) + "I did something else on {0}", date);
25 | }
26 |
27 |
28 | public static string DoSomethingOptional(int id, string data = "No Data Provided")
29 | {
30 | var result = string.Format(ConsoleFormatting.Indent(2) +
31 | "I did something to the record Id {0} and saved the data {1}", id, data);
32 |
33 | if(data == "No Data Provided")
34 | {
35 | result = string.Format(ConsoleFormatting.Indent(2) +
36 | "I did something to the record Id {0} but the optinal parameter "
37 | + "was not provided, so I saved the value '{1}'", id, data);
38 | }
39 | return result;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/ConsoleApplicationBase/ConsoleCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Text.RegularExpressions;
7 | using ConsoleApplicationBase.Commands;
8 |
9 | namespace ConsoleApplicationBase
10 | {
11 | public class ConsoleCommand
12 | {
13 | public ConsoleCommand(string input)
14 | {
15 | // Ugly regex to split string on spaces, but preserve quoted text intact:
16 | var stringArray =
17 | Regex.Split(input, "(?<=^[^\"]*(?:\"[^\"]*\"[^\"]*)*) (?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
18 |
19 | _arguments = new List();
20 | for (int i = 0; i < stringArray.Length; i++)
21 | {
22 | // The first element is always the command:
23 | if (i == 0)
24 | {
25 | this.Name = stringArray[i];
26 |
27 | // Set the default:
28 | this.LibraryClassName = "DefaultCommands";
29 | string[] s = stringArray[0].Split('.');
30 | if (s.Length == 2)
31 | {
32 | this.LibraryClassName = s[0];
33 | this.Name = s[1];
34 | }
35 | }
36 | else
37 | {
38 | var inputArgument = stringArray[i];
39 |
40 | // Assume that most of the time, the input argument is NOT quoted text:
41 | string argument = inputArgument;
42 |
43 | // Is the argument a quoted text string?
44 | var regex = new Regex("\"(.*?)\"", RegexOptions.Singleline);
45 | var match = regex.Match(inputArgument);
46 |
47 | // If it IS quoted, there will be at least one capture:
48 | if (match.Captures.Count > 0)
49 | {
50 | // Get the unquoted text from within the qoutes:
51 | var captureQuotedText = new Regex("[^\"]*[^\"]");
52 | var quoted = captureQuotedText.Match(match.Captures[0].Value);
53 |
54 | // The argument should include all text from between the quotes
55 | // as a single string:
56 | argument = quoted.Captures[0].Value;
57 | }
58 | _arguments.Add(argument);
59 | }
60 | }
61 | }
62 |
63 | public string Name { get; set; }
64 | public string LibraryClassName { get; set; }
65 |
66 | private List _arguments;
67 | public IEnumerable Arguments
68 | {
69 | get
70 | {
71 | return _arguments;
72 | }
73 | }
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/ConsoleApplicationBase/ConsoleApplicationBase.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {D21CC334-9E7D-4A29-B6F9-5120351EC703}
8 | Exe
9 | Properties
10 | ConsoleApplicationBase
11 | ConsoleApplicationBase
12 | v4.5
13 | 512
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | AnyCPU
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | A Useful, Interactive, and Extensible .NET Console Application Template for Development and Testing
2 | ===================================================================================================
3 |
4 | This application can serve as a template of framework into which you can easily plug in commands related tothe code you want to demo, excercise, or which forms the business layer of an actual Console application. The "Interactive" part of this Console is already in place - all you need to do is plug command classes and methods into the Commands namespace and you're ready to go.
5 |
6 | The goal here was not to emulate a fully-functioning Shell or terminal. I wanted to be able to:
7 |
8 | * Run the program, be greeted with a prompt (customizable, in this case), and then enter commands corresponding to various methods defined in a specific area of the application.
9 | * Receive feedback (where appropriate), error messages, and such
10 | * Easily add/remove commands
11 | * Generally be able to quickly put together a functioning console application, with predictable and familiar interactive behavior, without re-inventing the wheel every time.
12 |
13 | For more detailed information about how this works, see the Blog post at: [C#: Building a Useful, Extensible .NET Console Application Template for Development and Testing](http://typecastexception.com/post/2014/09/07/C-Building-a-Useful-Extensible-NET-Console-Application-Template-for-Development-and-Testing.aspx)
14 |
15 | Assumptions
16 | -----------
17 |
18 | As it is currently configured, this application makes a few assumptions about architecture. You can easily adapt things to suit your purposes, but out-of-the-box, the following "rules" are assumed:
19 |
20 | * Methods representing Console commands will be defined as `public static` methods which always return a `string`, and are defined on `public static` classes.
21 | * Classes containing methods representing Console commands will be located in the `Commands` namespace, and in the *Commands* folder.
22 | * There will always be a static class named `DefaultCommands` from which methods may be invoked from the Console directly by name. For many console projects, this will likely be sufficient.
23 | * Commands defined on classes other than DefaultCommands will be invoked from the console using the familiar dot syntax: ClassName.CommandName.
24 |
25 | Defining Commands
26 | -----------------
27 |
28 | If you were to define the following (trival example-style) commands in your `DefaultCommands` class, you will be able to execute these from the Console when you run the application. The DefaultCommands class must be present in the project, and must be within the `Commands` namespace (note that the methods must be `static` in order to be available to the console as commands, and the project assumes a `string` return type),
29 |
30 | ```csharp
31 | public static string DoSomething(int id, string data)
32 | {
33 | return string.Format(ConsoleFormatting.Indent(2) +
34 | "I did something to the record Id {0} and saved the data '{1}'", id, data);
35 | }
36 |
37 |
38 | public static string DoSomethingElse(DateTime date)
39 | {
40 | return string.Format(ConsoleFormatting.Indent(2) + "I did something else on {0}", date);
41 | }
42 |
43 |
44 | public static string DoSomethingOptional(int id, string data = "No Data Provided")
45 | {
46 | var result = string.Format(ConsoleFormatting.Indent(2) +
47 | "I did something to the record Id {0} and saved the data {1}", id, data);
48 |
49 | if(data == "No Data Provided")
50 | {
51 | result = string.Format(ConsoleFormatting.Indent(2) +
52 | "I did something to the record Id {0} but the optinal parameter "
53 | + "was not provided, so I saved the value '{1}'", id, data);
54 | }
55 | return result;
56 | }
57 | ```
58 |
59 | Executing Commands
60 | ------------------
61 |
62 | The commands above can be executed when you run the application with the following syntax:
63 |
64 | Execute the `DoSomething` command:
65 |
66 | ```
67 | console> DoSomething 55 "My Data"
68 | ```
69 |
70 | Execute the `DoSomethingElse` command:
71 |
72 | ```
73 | console> DoSomethingElse 7/4/2014
74 | ```
75 | The console recognizes and deals with optional method parameters.
76 |
77 | Execute the `DoSomethingOptional` command inluding optional parameters:
78 |
79 | ```
80 | console> DoSomethingOptional 212 "This is my optional data"
81 | ```
82 |
83 | OR, you could omit the last argument, and the default value defined on the method will be used:
84 |
85 | ```
86 | console> DoSomethingOptional 212
87 | ```
88 |
89 | I'm happy to take pull requests, suggestions, and ideas.
90 |
--------------------------------------------------------------------------------
/ConsoleApplicationBase/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Reflection;
7 |
8 | namespace ConsoleApplicationBase
9 | {
10 | class Program
11 | {
12 | const string _commandNamespace = "ConsoleApplicationBase.Commands";
13 | static Dictionary>> _commandLibraries;
14 |
15 | static void Main(string[] args)
16 | {
17 | Console.Title = typeof(Program).Name;
18 |
19 | // Any static classes containing commands for use from the
20 | // console are located in the Commands namespace. Load
21 | // references to each type in that namespace via reflection:
22 | _commandLibraries = new Dictionary>>();
24 |
25 | // Use reflection to load all of the classes in the Commands namespace:
26 | var q = from t in Assembly.GetExecutingAssembly().GetTypes()
27 | where t.IsClass && t.Namespace == _commandNamespace
28 | select t;
29 | var commandClasses = q.ToList();
30 |
31 | foreach (var commandClass in commandClasses)
32 | {
33 | // Load the method info from each class into a dictionary:
34 | var methods = commandClass.GetMethods(BindingFlags.Static | BindingFlags.Public);
35 | var methodDictionary = new Dictionary>();
36 | foreach (var method in methods)
37 | {
38 | string commandName = method.Name;
39 | methodDictionary.Add(commandName, method.GetParameters());
40 | }
41 | // Add the dictionary of methods for the current class into a dictionary of command classes:
42 | _commandLibraries.Add(commandClass.Name, methodDictionary);
43 | }
44 | Run();
45 | }
46 |
47 |
48 | static void Run()
49 | {
50 | while (true)
51 | {
52 | var consoleInput = ReadFromConsole();
53 | if (string.IsNullOrWhiteSpace(consoleInput)) continue;
54 |
55 | try
56 | {
57 | // Create a ConsoleCommand instance:
58 | var cmd = new ConsoleCommand(consoleInput);
59 |
60 | // Execute the command:
61 | string result = Execute(cmd);
62 |
63 | // Write out the result:
64 | WriteToConsole(result);
65 | }
66 | catch (Exception ex)
67 | {
68 | // OOPS! Something went wrong - Write out the problem:
69 | WriteToConsole(ex.Message);
70 | }
71 | }
72 | }
73 |
74 |
75 | static string Execute(ConsoleCommand command)
76 | {
77 | // Validate the class name and command name:
78 | // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
79 |
80 | string badCommandMessage = string.Format(""
81 | + "Unrecognized command \'{0}.{1}\'. "
82 | + "Please type a valid command.",
83 | command.LibraryClassName, command.Name);
84 |
85 | // Validate the command name:
86 | if (!_commandLibraries.ContainsKey(command.LibraryClassName))
87 | {
88 | return badCommandMessage;
89 | }
90 | var methodDictionary = _commandLibraries[command.LibraryClassName];
91 | if (!methodDictionary.ContainsKey(command.Name))
92 | {
93 | return badCommandMessage;
94 | }
95 |
96 | // Make sure the corret number of required arguments are provided:
97 | // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
98 |
99 | var methodParameterValueList = new List