Introduction
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.
Background
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.Core.dll
and WizardWrx.OperatingParameterManager.dll
, and adding corresponding using
statements to Program.cs
.
using WizardWrx.Core;
using WizardWrx.OperatingParameterManager;
Next, create the OperatingParametersCollection
object.
OperatingParametersCollection<ParameterType , ParameterSource> operatingParametersColl = OperatingParametersCollection<ParameterType , ParameterSource>.GetTheSingleInstance (
Properties.Settings.Default.Properties ,
Properties.Resources.PARAMETER_DISPLAY_NAME_TEMPLATE ,
ParameterSource.ApplicationSettings );
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. - The
ParameterType
enumeration drives the switch block in IsValueValid
that implements the validation engine. - The
ParameterSource
and 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 ( ) ,
CmdLneArgsBasic.ArgMatching.CaseInsensitive );
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.
operatingParametersColl.SetFromCommandLineArguments (
cmdArgs ,
ParameterSource.CommandLine );
Method 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 ParameterSource
enumeration. - 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
ParamSource
property.
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> ( );
Console.WriteLine (
Properties.Resources.MSG_VALIDATION_DETAIL ,
operatingParameter.DisplayName ,
Utl.RenderStringValue (
operatingParameter.ParamValue ) ,
fParamIsValid );
}
A simple 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 True
or False
, and sets the value of its ParamState
property to ParameterState.Validated
when it returns True
.
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.
InternalName | ParamType |
Table 1 lists the parameters for the demonstration program. 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.
Road Map
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.
History
Monday, 03 September 2018 is the release date of this first edition.