12 May, 2010

How to create a plugin framework

I was quite recently faced with the task of creating a “pluggable system” for windows mobile application. Now – this can seem a pretty straightforward thing; but I was actually surprised by the challenges involved therein. Anyway I ended up with quite a good and robust framework. So as a point of reference for myself, the recipe is posted on this blog.

I prefer to adhere to the KISS-principle (Keep It Simple Stupid!) to the extent that makes sense, and the same applies here. Keep it as simple as possible; in this way it usually ends up working and you can always later extend the implementation to satisfy more advanced scenarios if needed. The final Proof of Concept is seen here:

Link to sample project
: Press here

image image

The setup is this:
image

These two interfaces are the main players in the system. The IPlugin is the interface to be implemented by the plugins classes (usercontrols); whereas the IPluginHost is the interface to be implemented by the Host (winforms-application).

IPluginHost
This interface has 2 methods. The RegisterPlugin method registers the plugin into a local collection of available plugins that the host can show. The ShowPlugin method displays the plugin (identified by it’s type) in the hosting form.

IPlugin
This interface has merely 3 simple properties that defines a plugin in this framework. The DisplayTitle is the name to show for the plugin, the Identity is the unique identification of the plugin and the MainInterface is a reference to the usercontrol itself.

Command pattern:
To make the framework a bit more advanced, I in addition want the plugins to be able to interact with the 2 buttons found on the hosting form. To enable this scenario, I have employed the Command pattern.
image

A plugin can decide to implement button support by implementing the IButtonSupport interface. It is a conscious decision from a plugin to implement buttonsupport, not something you are forced to support. Remember that composition is preferred over inheritance according to best practice for software design.

This interface (IButtonSupport) exposes 2 properties (LeftCmd and RightCmd) and a setup method that ties the command to the hosting form (IPluginHost) as the physical buttons are located on this form. The 2 properties LeftCmd and RightCmd are of type ICommand which has a DisplayText and an Execute() method.


Host:
To allow the host to load the plugins, it needs to load them first. This is seen in LoadPlugins() method.

public partial class MainForm : Form, IPluginHost
{
List<IPlugin> m_plugins;
ICommand m_LeftCmd;
ICommand m_RightCmd;

public MainForm()
{
InitializeComponent();

//get plugins
LoadPlugins();
}

void LoadPlugins()
{
m_plugins = new List<IPlugin>();

//get list from finder
var pf = new PluginLoader();
var lst = pf.GetApplicablePlugins();

//register the plugins
foreach (var item in lst)
RegisterPlugin(item);
}

#region IPluginHost Members

public bool RegisterPlugin(IPlugin plugin)
{
//set behavior
plugin.MainInterface.Dock = DockStyle.Fill;

//add to list
m_plugins.Add(plugin);

//add to listbox (UI)
listBox1.DisplayMember = "DisplayTitle";
listBox1.Items.Add(plugin);

return true; //success
}

public void ShowPlugin(Type identity)
{
//find plugin amoun the registared plugins
IPlugin pl = m_plugins.Where(p => p.Identity == identity).FirstOrDefault();
if (pl == null)
return;

lblPTitleValue.Text = pl.DisplayTitle;


//disable buttons (default)
btnLeft.Enabled = false;
btnRight.Enabled = false;
btnLeft.Text = "<text>";
btnRight.Text = "<text>";

//button support?
var bs = pl as IButtonSupport;
if (bs != null)
{
//set host (to allow buttons to work)
bs.SetupButtons(this);

m_LeftCmd = bs.LeftCmd;
m_RightCmd = bs.RightCmd;

btnLeft.Enabled = true;
btnLeft.Text = bs.LeftCmd.ButtonText;

btnRight.Enabled = true;
btnRight.Text = bs.RightCmd.ButtonText;
}

panel1.Controls.Clear();
panel1.Controls.Add(pl.MainInterface);
}

The PluginLoader used by the mainform, is seen here.

namespace Host
{
class PluginLoader
{
/// <summary>
///
Gets the applicable plugins.
/// </summary>
/// <returns></returns>
public List<IPlugin> GetApplicablePlugins()
{
//get files (for now - only identity 'Pl***")
var files = Directory.GetFiles(Application.StartupPath, "Pl*.dll");

var lst = new List<IPlugin>();
foreach (var file in files)
{
var fi = new FileInfo(file);
Debug.Assert(fi != null, "failed to create fileInfo");

var asm = Assembly.LoadFile(fi.FullName);
foreach (var type in asm.GetExportedTypes())
{
if (type.IsPublic) //only look in public types!
{
Type t = type.GetInterface("Contracts.IPlugin"); //search for interface
if(t != null)
lst.Add((IPlugin)Activator.CreateInstance(type));
}
}
}

Debug.Assert(lst.Count > 0, "no plugins found in collection");
return lst;
}
}
}
Plugins:
To allow a plugin to be loadable inside the host, the most basic implementation of IPlugin is seen here:
namespace Plugin2
{
public partial class UserControl1 : UserControl, IPlugin
{
public UserControl1()
{
InitializeComponent();
this.DisplayTitle = "Plugin2.UserControl1";
}

public UserControl MainInterface
{
get { return this; }
}

public Type Identity
{
get { return this.GetType(); }
}

public string DisplayTitle{get;set;}
}
}
If the plugin is also supporting buttonsupport, it needs to implement an additional interface (IButtonSupport) as well.
namespace Plugin1
{
public partial class UserControl1 : UserControl, IPlugin, IButtonSupport
{
ICommand m_left;
ICommand m_right;

public UserControl1()
{
InitializeComponent();
this.DisplayTitle = "Plugin1.UserControl1";
}

public UserControl MainInterface
{
get { return this; }
}

public ICommand LeftCmd
{
get { return m_left; }
}

public ICommand RightCmd
{
get { return m_right; }
}

/// <summary>
///
Sets the host.
/// </summary>
/// <param name="host">
The host.</param>
public void SetupButtons(IPluginHost host)
{
m_left = new ShowCmd(host);
m_right = new NullCmd();
}

public Type Identity
{
get { return this.GetType(); }
}

public string DisplayTitle {get;set;}
}
}
By using this framework, the plugins can do anything as long as they are implemented as UserControls and they are registered in the host. In addition – the plugins can interact with the hosting form without even knowing what they are communicating with. This is the very definition of a loosely coupled design!

No comments: