Creating a Simple Plugin Mechanism
Introduction
One of the recurring problems in software development is a plugin mechanism to extending the application without changing existing code. So you need only to deliver new assemblies. Also other developers could extend your application. There are several mechanisms to solve this problem. One way is to use an existing framework like MEF (Managed Extensibility Framework). But it is also possible to build a plugin mechanism from scratch.
The code of the following examples are available on the Developer Code Samples Gallery written in C#.
Creating a simple plugin mechanism (part 1)
Creating a simple plugin mechanism (part 2)
Creating a simple plugin mechanism from scratch
To solve this problem, a simple plugin mechanism is implemented that search and loads plugins from a predefined location. The created plugins are projects in form of built assemblies (DLLs).
Creating the plugin interface
First we need to define an Interface that all plugins must implement. This Interface is often included in an own project, so other developers only need the assembly of this project to write their own plugins. The members of the Interface depend on what your application is intended to do. In this sample we have one property that is returning a name and one method that is doing something.
namespace PluginContracts
{
public interface IPlugin
{
string Name { get; }
void Do();
}
}
Implementing the plugin interface
To provide a plugin, you have to create a new project and add a reference to PluginContracts. Then you have to implement IPlugin.
using PluginContracts;
namespace FirstPlugin
{
public class FirstPlugin : IPlugin
{
#region IPlugin Members
public string Name
{
get
{
return "First Plugin";
}
}
public void Do()
{
System.Windows.MessageBox.Show("Do Something in First Plugin");
}
#endregion
}
}
Implementing the plugin framework
Next we need to implement the framework in our main application that knows how to find and how to handle the plugins.
Searching for plugins
First of all we have to know where to search for plugins. Usually we will specify a folder in that all plugins are put in. In this folder we search for all assemblies.
string[] dllFileNames = null;
if(Directory.Exists(path))
{
dllFileNames = Directory.GetFiles(path, "*.dll");
}
Loading the plugins
Next we have to load the assemblies. Therefore we are using Reflections (System.Reflection).
ICollection<Assembly> assemblies = new List<Assembly>(dllFileNames.Length);
foreach(string dllFile in dllFileNames)
{
AssemblyName an = GetAssemblyName(dllFile);
Assembly assembly = Assembly.Load(an);
assemblies.Add(assembly);
}
Searching for plugin implementations
Now we have loaded all assemblies from our predefined location, we can search for all types that implement our Interface IPlugin.
Type pluginType = typeof(IPlugin);
ICollection<Type> pluginTypes = new List<Type>();
foreach(Assembly assembly in assemblies)
{
if(assembly != null)
{
Type[] types = assembly.GetTypes();
foreach(Type type in types)
{
if(type.IsInterface || type.IsAbstract)
{
continue;
}
else
{
if(type.GetInterface(pluginType.FullName) != null)
{
pluginTypes.Add(type);
}
}
}
}
}
Instantiate plugin implementations
Last we create instances from our found types using Reflections.
ICollection<IPlugin> plugins = new List<IPlugin>(pluginTypes.Count);
foreach(Type type in pluginTypes)
{
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
plugins.Add(plugin);
}
Integrate the plugin mechanism in the main application
In the main application we can use the implemented properties and methods of our plugins. To demonstrate that, we create a button for each loaded plugin and connect the content and the click event of the button to the property and the method of the plugin.
_Plugins = new Dictionary<string, IPlugin>();
ICollection<IPlugin> plugins = PluginLoader.LoadPlugins("Plugins");
foreach(var item in plugins)
{
_Plugins.Add(item.Name, item);
Button b = new Button();
b.Content = item.Name;
b.Click += b_Click;
PluginGrid.Children.Add(b);
}
We are using a Dictionary with the name of the plugin as key to memorize, which button content belongs to which plugin. So we can execute the correct plugin method on certain button click.
private void b_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
if(b != null)
{
string key = b.Content.ToString();
if(_Plugins.ContainsKey(key))
{
IPlugin plugin = _Plugins[key];
plugin.Do();
}
}
}
Creating a simple plugin mechanism with MEF
Creating a plugin mechanism with MEF differs not in all parts from creating a plugin mechanism from scratch. So only the differences are described in the next sections. Furthermore with the MEF, the plugin framework exists already and must not be implemented.
Implementing the plugin interface
In the source of the plugins that we want to provide, we have to make our first changes. We have to add the reference to System.ComponentModel.Composition and using System.ComponentModel.Composition. Now we can mark the class with the Export attribute. So the MEF will later find and process this class. Furthermore the type is explicit stated. Hence we want to use the interface rather than the concrete class in the main project.
using System.ComponentModel.Composition;
using PluginContracts;
namespace FirstPlugin
{
[Export(typeof(IPlugin))]
public class FirstPlugin : IPlugin
{
#region IPlugin Members
public string Name
{
get
{
return "First Plugin";
}
}
public void Do()
{
System.Windows.MessageBox.Show("Do Something in First Plugin");
}
#endregion
}
}
Using MEF in the main application
Next we need to implement the mechanism in our main application that knows how to find and how to handle the plugins. For that we are using methods provided by MEF.
So that we can use the Export marked parts, we have to mark properties with the Import attribute. In our case we want to use several exported parts, so we are using the ImportMany attribute.
[ImportMany]
public IEnumerable<IPlugin> Plugins
{
get;
set;
}
Last we need to search for the exported and imported parts and compose them. Therefore we specify a folder in that all plugins are put in.
public MEFPluginLoader(string path)
{
DirectoryCatalog directoryCatalog = new DirectoryCatalog(path);
// An aggregate catalog that combines multiple catalogs
var catalog = new AggregateCatalog(directoryCatalog);
// Create the CompositionContainer with all parts in the catalog (links Exports and Imports)
_Container = new CompositionContainer(catalog);
// Fill the imports of this object
_Container.ComposeParts(this);
}
So far we have searched and initialized our plugins, so we can use the implemented properties and methods of our plugins. To access our plugins we use the implemented MEFPluginLoader.
MEFPluginLoader loader = new MEFPluginLoader("Plugins");
IEnumerable<IPlugin> plugins = loader.Plugins;
Improvement
So that we do not need to implement for each new solution its own plugin loader, we use Generics. This allows us to remove the dependence from a certain plugin interface (here IPlugin).
With that we could also use the same plugin loader to resolve several types of plugins in the same project. So we could have an ICalculationPlugin, IExporterPlugin, ISomethingElsePlugin. All these plugin interfaces can have their own properties and methods. They can be accessed from different places in different cases.
Type pluginType = typeof(T);
ICollection<Type> pluginTypes = new List<Type>();
foreach(Assembly assembly in assemblies)
{
if(assembly != null)
{
Type[] types = assembly.GetTypes();
foreach(Type type in types)
{
if(type.IsInterface || type.IsAbstract)
{
continue;
}
else
{
if(type.GetInterface(pluginType.FullName) != null)
{
pluginTypes.Add(type);
}
}
}
}
}
ICollection<T> plugins = new List<T>(pluginTypes.Count);
foreach(Type type in pluginTypes)
{
T plugin = (T)Activator.CreateInstance(type);
plugins.Add(plugin);
}
References
- http://code.msdn.microsoft.com/Creating-a-simple-plugin-b6174b62. Code of "Creating a simple plugin mechanism"
- http://code.msdn.microsoft.com/Creating-a-simple-plugin-b45f1d4e. Code of "Creating a simple plugin mechanism (part 2)"
- Furrher articles about .NET, C# and WPF can be found on my blog http://chrigas.blogspot.com/
See Also
An important place to find a huge amount of Visual C# related articles is the TechNet Wiki itself. The best entry point is Visual C# Resources on the TechNet Wiki