Building Plugins with C# Part 2: Making Additions with XML
This article focuses on two tasks. First, I'll add a new plug-in, which uses an XML file to find valid users. Second, I'll add a new configuration section handler to the simple UI testing program, to be used for loading the plug-ins when the application starts up.
The New XML Plug-in
Since the common classes are already built, developing the new XML authentication plug-in doesn't take much effort. Like the first test plug-in, this one will go in its own project in the solution.
The first step is to create that project, which I called AuthenticationPlugin.Plugins.XmlAuthenticator. I also added a project reference to the AuthenticationPlugin.Common project in the same solution, and named the new class Plugin.
For the UI to load and use the plug-in, the main class that is going to do the work must be labeled with the AuthenticationPlugin attribute and it must implement the IAuthenticationPlugin interface. The AuthenticationPlugin attribute helps the UI application to identify which class to load an instance of, and by implementing the IAuthenticationPlugin interface the application can be sure that the IsLoginValid method is declared. The new plug-in, with no implementation code, will look like this:
using System; using AuthenticationPlugin.Common; namespace AuthenticationPlugin.Plugins.XmlAuthenticator { /// <summary> /// XML authentication plugin /// </summary> [AuthenticationPlugin] public class Plugin : IAuthenticationPlugin { public bool IsLoginValid( string username, string password ) { bool isLoginValid = false; return isLoginValid; } } }
As it is listed above, the plug-in will compile and the application can use it. Of course, no users will be authenticated because the value returned will always be false until we add real code to the plug-in. The initialization and return of isLoginValid (set to false) is just useful to keep the compiler from whining about there being no return value in the IsLoginValid method.
Now it's time to add the meat of the plug-in: the code that actually does the work. Since this plug-in uses XML, I added the following line to the top of the Plugin.cs file under using System;
using System.Xml;
Also, I check to see if the XML file exists and, using the Assembly class, get the plug-in's current directory so it can find the XML file. These lines go at the top of the file with the using System.XML line:
using System.IO; using System.Reflection;
A sample of the XML file used to hold the information is listed here, and saved in the project as users.xml:
<?xml version="1.0" encoding="utf-8" ?> <users> <user name="asmith" password="xxx" /> <user name="nuser" password="secret" /> </users>
The root node is users, which contains user nodes with name and password attributes. The name attribute will be the value against which username is compared, and the password attribute holds the user's password.
The method that searches for the user in the XML file is IsUserInXmlFile method found in the code shown in Listing 1-1. After making a check to see if the passed-in file name is a file that exists using the File.Exists method, it loads the file using the XmlDocument.Load method, then uses an XPath to build a query to search for the user:
string xPath = string.Format( "//users/user[@name='{0}' and @password='{1}']", username, password );
An XPath is a string that can be used a lot like a SQL statement in a database. With an XPath, a node can be selected by name, attribute value, and many other criteria. The parent and child nodes are separated by "/".
The result is a string that looks like //users/user[@name="user" and @password="secret"], assuming the username is user and the password is secret. The attributes are in brackets immediately after the node name, which is user in this case. The root node, users, follows //. Since both the username and password much match exactly, this is a fairly straight-forward comparison which is why attempting to select the node using XPath works well. If the node is found, it means a matching user is found in the XML file. If the node is null, it means the username and password combination was not found.
Alternatively, the method could loop through all of the child nodes and make comparisons, but there is little point in adding that much code for this type of task.
I altered the IsLoginValid method in Listing 1-1; it now has a step to find the users.xml file in the same directory where the plug-in resides. This introduces a dependency: the file must be located in the same directory or it won't work. Ideally, the location of the XML file would be configurable. I'll get into building configurations for the separate plug-ins in Part 3 of this series, so for now the XML file loads using hard-coded strings.
In a production system, the use of a DTD to define the users.xml file should be taken under consideration to make sure the file is valid.
Listing 1-1
using System; using System.IO; using System.Reflection; using System.Xml; using AuthenticationPlugin.Common; namespace AuthenticationPlugin.Plugins.XmlAuthenticator { /// <summary> /// XML authentication plugin /// </summary> [AuthenticationPlugin] public class Plugin : IAuthenticationPlugin { public bool IsLoginValid( string username, string password ) { string currentDir = Path.GetDirectoryName ( Assembly.GetExecutingAssembly().Location ); string xmlFile = Path.Combine( currentDir, "users.xml" ); bool isLoginValid = false; isLoginValid = IsUserInXmlFile( xmlFile, username, password ); return isLoginValid; } private bool IsUserInXmlFile ( string xmlFileName, string username, string password ) { bool isUserFound = false; /* Load up the XML file */ if ( File.Exists( xmlFileName ) ) { try { XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load( xmlFileName ); /* Construct an XPath to use for querying the XML file */ string xPath = string.Format( "//users/user[@name='{0}' and @password='{1}']", username, password ); /* Use XPath to find the user name */ XmlNode userNode = xmlDoc.SelectSingleNode( xPath ); isUserFound = ( null != userNode ); } catch { /* TODO: Log error here */ } } return isUserFound; } } }
Testing the New Plug-in
At this point, the XML authentication plug-in is complete and ready to be tested. You can put the AuthenticationPlugin.Plugins.XmlAuthenticator.dll file into a folder, such as C:\Temp. (Make sure to copy the users.xml file into the same folder as the plug-in.) Now, start the AuthenticationPlugin.Plugins.UI.UITester application and browse to the location of the AuthenticationPlugin.Plugins.XmlAuthenticator.dll plug-in. If you added users to the users.xml file, the "Login was successful!" message box pops up when the Login button is pressed.