This article demonstrates a class library, implemented as a Microsoft .NET DLL that targets the Microsoft .NET Framework, version 4.6.1 and the C# compiler, version 7.3. If you have any version of Visual Studio 2017, and your installation is up to date, you should have the correct framework and compiler. Otherwise, the project won't build, because it is configured to require the correct C# compiler version.
The library demonstrated in this article and included in the accompanying demonstration archive represents the culmination of work that began over 10 years ago, and is the latest of many attempts to achieve the following goals in a way that is as close to "plug and play" as I can make it.
- Data Driven: The library is entirely data driven; parameters are defined in a text resource that is embedded in the entry assembly to which the parameters apply.
- Supports Localization: Display names shown on output may be defined as text strings that are read from the string reosurces embedded in the entry assembly or a satellite assembly that corresponds to a second user interface language. Display names are optional; the internal parameter name is substituted for omitted display names.
- Supports Validation by Rules: Validation rules and a validation engine are built into the class.
- Extensible Validation Engine: Since it is implemented as an abstract method, the validation engine can be replaced if you need rules not covered by the sample.
- Implemented as a Class Library: Implementing it as a class library simplifies incorporation into new applications; you need only to add a reference and create the embedded text file resource that defines your parameters.
Using the code
Begin by setting references to
WizardWrx.OperatingParameterManager.dll, and adding corresponding
using statements to
Next, create the
OperatingParametersCollection<ParameterType , ParameterSource> operatingParametersColl = OperatingParametersCollection<ParameterType , ParameterSource>.GetTheSingleInstance (
The above statement requires a bit of explaining.
- Since the
OperatingParametersCollection class implements the Singleton design pattern, you must call the static
GetTheSingleInstance method on it to get a reference to the one and only instance. Creating the
OperatingParametersCollection as a singleton means that other routines defined in the same or other assemblies can use the parameters without littering your application with duplicate copies, not to mention saving the considerable overhead required to initialize them.
- Not only is
OperatingParametersCollection a singleton, but it is a generic that has not one, but two generic types, both of which are
Enums. Version 7,3 of the C# compiler is the first to support enumerations as constraints for generics.
Properties.Settings.Default.Properties is a
SettingsPropertyCollection that makes the application settings available for use by the
OperatingParametersCollection. Using this property instead of
Properties.Settings.Default decouples the
SettingsPropertyCollection from the assembly's default namespace, so that the library can support the settings of any application, at the expense of the individual configuration properties, which the library cannot use without defeating its main objective.
Properties.Resources.PARAMETER_DISPLAY_NAME_TEMPLATE is a string that I put into the resource strings of the entry assembly, along with strings that represent names of two of the three parameters. (I omitted the third to demonstrate what happens when a display name is omitted. In the real world, I would define display name strings for all of them.)
ParameterSource.ApplicationSettings is a member of the
ParameterSource enumeration, which I defined at namespace scope in the
OperatingParameter class source file, which is in the library. This value is stored with a parameter that has a default value defined in the application settings to indicate the source from whence the value came.
HasDefaultValueInAppSettings, a public Boolean property, is set to
True to indicate that the parameter has a default value. Another member of the enumeration,
CommandLine, is intended for marking parameters that get their values from named parameters in the command line.
ParameterType enumeration drives the switch block in
IsValueValid that implements the validation engine.
ParameterType enumerations may be defined anywhere, but putting the definitions logically and physically close to the implementation of the
IsValueValid method on
OperatingParameterBase simplifes its definition a bit, while keeping the definitions of the two enumerations that take the place of the two generics together.
The next object to be defined is a
WizardWrx.Core.CmdLneArgsBasic object, which is defined as follows.
CmdLneArgsBasic cmdArgs = new CmdLneArgsBasic (
operatingParametersColl.GetParameterNames ( ) ,
The constructor takes two arguments.
- Instance method
operatingParametersColl.GetParameterNames ( ) returns an array of strings, each of which represents the internal name of an operating parameter. This list is fed into the constructor, which uses it to initialize the collection that it uses to parse the command line and return parameter values as needed.
- Enumeration type
CmdLneArgsBasic.ArgMatching.CaseInsensitive instructs the
CmdLneArgsBasic object to treat the parameters as case insensitive. Although the
OperatingParametersCollection object is case sensitive, I usually adhere to the established Windows command line convention that parameter names are not.
- Since the
CmdLneArgsBasic constructor parses the command line, the object is immediately ready to return parameter values.
- Astute readers will notice that the args array is conspicuously absent from the argument list that goes into the
CmdLneArgsBasic constructor. This is possible because the constructor gets them from the
System.Environment singleton, which makes them available to both console and graphical mode programs. This feature makes it easy to write Windows programs that take command line arguments, of which I have many in my personal tool kit. Another benefit of this constructor design is that a
CmdLneArgsBasic constructor can be called from anywhere, since it ignores the args array.
The third preparatory step sets operating parameters that either have no default, or had their default values overrridden by command line parameters.
SetFromCommandLineArguments feeds the command line arguments collection stored in the
CmdLneArgsBasic object into a routine that iterates the collection, matches names with defined parameters, and sets or updates their values.
- Operating parameter values that are either initialized or overridden by command line arguments are tagged with the
CommandLine member of the
- If the value supplied by the command line parameter overrides a default that was set from the application configuration, the original value is preserved by copying it into the public
The fourth, and last, preparatory step is to validate the parameters.
foreach ( string strParamName in operatingParametersColl.GetParameterNames ( ) )
OperatingParameter<ParameterType , ParameterSource> operatingParameter = operatingParametersColl.GetParameterByName ( strParamName );
bool fParamIsValid = operatingParameter.IsValueValid<ParameterType> ( );
operatingParameter.ParamValue ) ,
ForEach loop iterates the array of parameter names returned by
GetParameterNames, then gets a reference to each
OperatingParameter object in turn by calling
GetParameterByName. The paramter value is validated by calling instance method
IsValueValid<ParameterType> ( ), which returns
False, and sets the value of its
ParamState property to
ParameterState.Validated when it returns
Since it applies to every object derived from it, the ParameterState enumeration is defined on the
OperatingParameterBase class, which belongs to the library.
Though the routine that sets a value could validate it, I chose to decouple the two functions, so that the parameters can be loaded and listed before any of them are validated. Separating the validation gives you the option of stopping after the first invalid parameter is identified, or completing the evaluation before stopping.
Strictly speaking, there is a fifth step, but it's a design time step, which is defining the parameters and creating the text file that drives the whole process.
Table 1 lists the parameters for the demonstration program.
|InternalName ||ParamType |
|OperatingParameter1 ||ExistingDirectory |
|OperatingParameter2 ||ExistingFile |
|OperatingParameter3 ||NewFile |
The table is constructed as a TAB delimited text file that contains two columns, a label row, and a detail row for each parameter. Since this demonstration program defines 3 parameters, its table contains 3 rows. I usually lay out such files in Microsoft Excel worksheets, which conveniently convert to tab delimited text when copied onto the Windows Clipboard, from which the text is pasted into a Visual Studio text editor window to create the embedded resource. Since the parser knows how to handle Byte Order Marks, the UTF-8 BOM added by the Visual Studio text editor causes no interference.
Before the text file can be used, it must be embedded into the assembly as a text resource; this is accomplished by setting its Build Action to Embedded Resource. The build engine takes it from there.
Points of Interest
There is so much going on under the hood that I must either provide an exhaustive list, at the expense of making this article dreaffully long and boring, or hit a few high points, and invite you to explore on your own.
I make no apologies for the fact that there is a great deal more happening under the covers than space permits me to cover. However, every class, property, and method has XML help that shows up in IntelliSense, most of which is meticulously cross referenced. I've also included the PDB files for every custom assembly, nothing is obfuscated, and it's all free to use under a standard 3-clause BSD license. A few weeks after I published this article, I finally got the source code in WizardWrx .NET API, which contains most of the libraries used by this project updated. Yesterday, I updated the library to include a handful of new methods and a new string constant. Since this library is unaffected by the changes, all of which are new code, and I haven't rebuilt the library since first publication, the article archive is unchanged.
The road map for the library is straightforward. I expect future versions to add types to the
ParameterType enumeration to support additional rules in an improved
IsValueValid routine. Among the additions under consideration are additional validations against the file system and evaluation against a basic regular expression.
Monday, 03 September 2018 is the release date of this first edition.
Monday, 08 October 2018, I added a link to the source code of the WizardWrx .NET API.