For an upcoming project I want to make use of a simple plugin architecture. The application I’m working on processes data for several different clients. Each client will have different rules, and I’m aiming for a data processing plugin for each client.

From a deployment perspective I want to load all my plugins from a single folder, and each plugin should be able to have its own config file in the same location.

The theory behind a plugin architecture is simple enough. An interface is used to identify a plugin and provide a common method of calling the plugin – doing some work. Plugins are loaded by checking assemblies in the plugins folder, and identifying any types that implement the interface. The plugin types are stored in some sort of keyed collection and created as needed. In order to provide the collection key, we’re going to decorate each plugin with an attribute.

IPlugin.cs

Every plugin must implement this interface.

public interface IPlugin
{
    string DoStuff();
}

ClientNameAttribute.cs

Each plugin will also be decorated by this attribute.

[AttributeUsage(AttributeTargets.Class)]
public class ClientNameAttribute
{
    public ClientNameAttribute(string clientName) : base()
    {
        this.ClientName = clientName;
    }

    public string ClientName { get; private set; }

    public override string ToString()
    {
        return this.ClientName;
    }
}

Plugins.cs

The final bit of the basic architecture is the Plugins class, which loads and stores the plugin types for later.

All the interesting stuff takes place in the static Load method, which is the only way of obtaining a new Plugins instance.

All the dll files in the plugins folder are checked and any non-abstract types that implement the IPlugin interface and are decorated with the ClientNameAttribute, and the types are added to a dictionary.

Later we can either use the GetPluginType method to get the type associated with a client name, or the CreatePlugin method to create a new plugin instance.

public sealed class Plugins
{
    private Dictionary<string, Type> pluginDictionary =
        new Dictionary<string, Type>();

    private Plugins() { }

    public IPlugin CreatePlugin(string clientName)
    {
        Type type = this.GetPluginType(clientName);
        return (IPlugin)Activator.CreateInstance(type);
    }

    public Type GetPluginType(string clientName)
    {
        return pluginDictionary[clientName];
    }

    public IEnumerable<string> ClientNames
    {
        get { return pluginDictionary.Keys.AsEnumerable(); }
    }

    public static Plugins Load(string pluginFolderPath)
    {
        Plugins plugins = new Plugins();

        string[] files = Directory.GetFiles(pluginFolderPath, "*.dll");

        foreach (string filename in files)
        {
            var typeInfo =
                from t in Assembly.LoadFrom(filename).GetTypes()
                where !t.IsAbstract
                && t.GetInterfaces().Contains(typeof(IPlugin))
                && t.GetCustomAttributes(
                    typeof(ClientNameAttribute), false).Length == 1
                select new
                {
                    ClientNameAttrib = (ClientNameAttribute)
                        t.GetCustomAttributes(
                        typeof(ClientNameAttribute), false)[0],
                    Type = t
                };

            foreach (var item in typeInfo)
                plugins.pluginDictionary.Add(
                    item.ClientNameAttrib.ClientName, item.Type);
        }

        return plugins;
    }
}

Adding support for configuration

To add configuration support, we’ll add an abstract ConfigurablePlugin type.

There are a couple of base constructors that a child class could call, one where you specify the location of the config file, and a parameterless constructor where the assumption is that the config file will be in the same location as the assembly containing the child class.

public abstract class ConfigurablePlugin : IPlugin
{
    public ConfigurablePlugin()
        : this(Assembly.GetCallingAssembly().Location) { }

    public ConfigurablePlugin(string exePath)
    {
        this.Configuration =
            ConfigurationManager.OpenExeConfiguration(exePath);
    }

    protected Configuration Configuration { get; private set; }

    public abstract string DoStuff();
}

Source Code

Watch this space.