Building Plugins with C# Part 3: Adding LDAP, SQL, and Configuration
This is the third article in a four-part series on building plug-ins with C# .NET. The second part in the series covered some XML-related topics, such as using an XML file to store usernames and passwords, and writing a custom XML configuration section handler to parse the application configuration file.
In this article, I'll show you how to build two new plug-ins that require configuration. The first plug-in requires an LDAP root and the name of a domain in which to find a user. The second requires a database connection string. Aside from the configuration concepts, the other concepts that I'll introduce are using directory services like LDAP to authenticate against Active Directory, and using classes in the System.Data.SqlClient
namespace to connect to a database.
Configuration Issues
When the plug-ins are loaded by the testing app, AuthenticationPlugin.UI.UITester
, a call to
ConfigurationSettings.AppSettings
will pull configuration values from the application's configuration file, even if an XML configuration file was added correctly to the plug-in project. This can be seen by the Debug
statement added to the MyTest
test plug-in, which prints the location of the configuration file using the built-in .NET AppDomain
class:
Debug.WriteLine ( AppDomain.CurrentDomain.SetupInformation.ConfigurationFile );
The Debug
statement above prints the full name of the file that is being used as the configuration file, which will be the Application's configuration file.
This presents a slight problem. To add configurable values to one of the plug-ins, I could simply add the configuration keys to the application file, and I could even keep the file separate but include it using:
<appSettings file="plugin.config" />
However, with many different plug-ins, the potential exists for configuration keys to overlap. For instance, if more than one plug-in uses a database connection, I could easily have two configuration strings. One way to get around this is to add the full namespace and classname to the front of the configuration key, like this:
<appSettings> <add key="AuthenticationPlugin.Plugins.SqlAuthenticator.connectionString" value="Database=…" /> </appSettings>
Although this can work fine, the main reason why you might not want to configure the plug-ins this way is because it requires you to modify the application's configuration file. A requirement to add values to the configuration file conflicts with the intended concept: add the plug-in, and use it right away.
To solve this configuration dilemma, I wrote a base class that can be extended by any plug-in that requires configuration. Using a base class allows both our two new plug-ins, the LDAP and database authentication plug-ins, to take advantage of the same code to load configuration.
The base class uses an XML reader to iterate through a file and add any elements it finds under the base element to a NameValueCollection
—a class that comes with .NET in the Systems.Collections.Specialty
namespace. The base class uses the Assembly's current directory and name to compose a full configuration file filename by tacking on an extension. I chose ".config" as the file extension, but you could change it to make more sense for your implementation.
The LoadConfiguration
method from the base class is shown in Listing 1-1. The LoadConfiguration
method accepts the name of the configuration file's root node as a string and returns a new NameValueCollection
object with the element names and their values. A sample file, listed here, results in a NameValueCollection
with two key value pairs in it: valueA, with the value of Foo, and valueB with the value of Bar.
<?xml version="1.0"?> <rootElement> <valueA>Foo</valueA> <valueB>Bar</valueB> </rootElement>
An XMLTextReader reads in the configuration file. The reader iterates through the XML nodes and looks to see if the node is of type Element (other types include Comment, Document, Text, and more). Once an Element is found (otherwise known as a tag), the name of the element is added to the NameValueCollection
along with the text inside the element. The root element, identified by the single parameter to the LoadConfiguration
method, is skipped because we don't want it in the configuration key-value pair.
The base class is added to the AuthenticationPlugin.Common
project so other plug-ins can extend from it easily; they will all need to refer to the project anyway, to implement the IAuthenticationPlugin
interface and define the AuthenticationPlugin
attribute.
Listing 1-1
protected NameValueCollection LoadConfiguration( string rootNodeName ) { NameValueCollection config = new NameValueCollection(); /* It's not implemented for this example for the sake of * brevity, but this method really should check to see * if the file exists and log the error somewhere if it * doesn't. */ Debug.WriteLine( Assembly.GetCallingAssembly().Location ); XmlTextReader configReader = new XmlTextReader( Assembly.GetCallingAssembly().Location + ".config" ); try { /* While the file is not at the end */ while ( ! configReader.EOF ) { /* In this specific configuration file, the code is * looking for elements */ if ( XmlNodeType.Element == configReader.MoveToContent() && configReader.Name != rootNodeName ) { /* Add the configuration to the internal NameValueCollection object */ config.Add( configReader.Name, configReader.ReadElementString() ); } else { /* Advance through the file if we're not interested in * the current node */ configReader.Read(); } } } finally { /* It is very important to close the file */ if ( null != configReader ) { configReader.Close(); } } return config; }