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
The setup is this:
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.
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 HostPlugins:
{
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;
}
}
}
To allow a plugin to be loadable inside the host, the most basic implementation of IPlugin is seen here:
namespace Plugin2If the plugin is also supporting buttonsupport, it needs to implement an additional interface (IButtonSupport) as well.
{
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;}
}
}
namespace Plugin1By 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!
{
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;}
}
}