The conception, birth, and first steps of an application named Charlie

Subscribe: Atom or RSS

A Bucket Full of Plugins

by Alister Jones (SomeNewKid)

In the previous weblog entry I said that Charlie included a hack where the name of each plugin was hard-coded. The example given was as follows:

// Security Plugin
ISecurityPlugin securityPlugin =
    (ISecurityPlugin)pluginFactory.InstantiatePlugin(
        "Charlie.Security.SecurityPlugin, Charlie.Security");

In the above code, the string parameter consists of two parts. The first part of ‘Charlie.Security.SecurityPlugin’ is the name of the class to load up, and the second part of ‘Charlie.Security’ is the name of the assembly in which this class can be found. With these two bits of information, Charlie can create an instance of the Security plugin.

An easy solution to removing the hack would be to relocate this string information to the Web.config file. In that way, the required string value is no longer hard-coded. But there is a problem with such a simple solution. The problem is that the Web project must still have intimate knowledge of how all of the plugins are structured. The Web project must know both the names of plugin classes and the names of the assemblies. This somewhat defeats the purpose of plugins, which is that Charlie can operate without intimate knowledge of its plugins.

I decided that each plugin would be given a friendly name (such as ‘Charlie.Security’), and each would be referenced by its friendly name. This removes the need for plugins to be referenced by class names and assembly names. In turn this allows plugins can change their structure without affecting Charlie’s ability to use them.

I started by creating a new PluginInfo attribute which, when applied, looks like this:

namespace Charlie.Security
{
    [PluginInfo("Charlie.Security")]
    public class SecurityPlugin : ISecurityPlugin, IPlugin
    {
    }
}

That’s all well and good, but how can Charlie find plugins when all it has is a friendly name, and does not have the assembly name or class name? I applied a technique I had seen previously in Sprocket. When Charlie first loads up, it inspects all the assemblies in the \bin folder, and picks out any classes that implement the IPlugin interface. Charlie then maintains a record of each plugin and the friendly name associated with that plugin. Now if Charlie is asked to use the ‘Custom.Security’ plugin, it knows which plugin goes by that friendly name. In other words, Charlie is no longer told the assembly names and class names of plugins to use, but now finds this information for itself. The cut-down version of the code looks like this:

foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    foreach (Type type in assembly.GetTypes())
    {
        if (type.GetInterface("IPlugin") != null)
        {
            ConstructorInfo constructor = type.GetConstructor(new Type[0]);
            Object[] attributes = 
                type.GetCustomAttributes(typeof(PluginInfo), true);
            PluginInfo info = null;
            if (attributes != null && attributes.Length > 0)
            {
                info = (PluginInfo)attributes[0];
            }
            String name = (info != null) ? info.Name : String.Empty;
            /*
             *  At this point we know this type is a plugin, we know
             *  its friendly name, and we have its constructor.
             *  Charlie now stores the name and constructor as a
             *  key/value pair. This means that Charlie can create
             *  an instance of the plugin whenever it is passed its 
             *  friendly name.
             */
        }
    }
}

I then updated the Web.config file so that it passed in the friendly name of each plugin to use. Here is an example:

<add setting="DefaultSecurityPlugin" value="Charlie.Security" />

So what have we achieved here? Well, previously Charlie had to be told the name of the class to be used as its Security plugin, and the name of the assembly in which to find this class. If the class name or assembly name ever changed, Charlie would stop working. Now, Charlie only needs to know the friendly name of the Security plugin, and behind the scenes the plugin can change its name or assembly.

I then decided to push it one final step further. I noticed that all of the default plugins were Charlie plugins. A particular web site can specify another security plugin, such as ‘Customer.Security’, in the absence of which Charlie will default to its own plugin, which is ‘Charlie.Security’. I throught to myself, “Charlie knows the friendly names of its own default plugins—it does not have to be told these names.” So I removed the Web.config entries that specified the default plugins, and instead left it to Charlie to recognise one of its own. A particular web site can still specify an alternative, but otherwise Charlie uses its own default plugin.

So the hack has been removed. When it starts up, Charlie inspects the \bin folder and keeps a record of any plugins it finds. Charlie also recognises one of its own plugins, which it will use if a particular web site does not specify the use of an alternative plugin. Charlie no longer needs to be given a class name, assembly name, or friendly name. This is the sort of self-configuration that I am now trying to get Charlie to achieve. So far, so good.

by Alister Jones | Next up: Russian Dolls and X-ray Specs

0 comments

----