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).
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();
}
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;
FirstPlugin
class
FirstPlugin : IPlugin
#region IPlugin Members
Name
return
"First Plugin"
;
Do()
System.Windows.MessageBox.Show(
"Do Something in First Plugin"
);
#endregion
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.
[] dllFileNames =
null
if
(Directory.Exists(path))
dllFileNames = Directory.GetFiles(path,
"*.dll"
ICollection<Assembly> assemblies =
new
List<Assembly>(dllFileNames.Length);
foreach
(
dllFile
in
dllFileNames)
AssemblyName an = GetAssemblyName(dllFile);
Assembly assembly = Assembly.Load(an);
assemblies.Add(assembly);
Type pluginType =
typeof
(IPlugin);
ICollection<Type> pluginTypes =
List<Type>();
(Assembly assembly
assemblies)
(assembly !=
)
Type[] types = assembly.GetTypes();
(Type type
types)
(type.IsInterface || type.IsAbstract)
continue
else
(type.GetInterface(pluginType.FullName) !=
pluginTypes.Add(type);
ICollection<IPlugin> plugins =
List<IPlugin>(pluginTypes.Count);
pluginTypes)
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
plugins.Add(plugin);
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 =
Dictionary<
, IPlugin>();
ICollection<IPlugin> plugins = PluginLoader.LoadPlugins(
"Plugins"
(var item
plugins)
_Plugins.Add(item.Name, item);
Button b =
Button();
b.Content = item.Name;
b.Click += b_Click;
PluginGrid.Children.Add(b);
private
b_Click(
object
sender, RoutedEventArgs e)
Button b = sender
as
Button;
(b !=
key = b.Content.ToString();
(_Plugins.ContainsKey(key))
IPlugin plugin = _Plugins[key];
plugin.Do();
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.
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.
System.ComponentModel.Composition;
[Export(
(IPlugin))]
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]
IEnumerable<IPlugin> Plugins
set
MEFPluginLoader(
path)
DirectoryCatalog directoryCatalog =
DirectoryCatalog(path);
//An aggregate catalog that combines multiple catalogs
var catalog =
AggregateCatalog(directoryCatalog);
// Create the CompositionContainer with all parts in the catalog (links Exports and Imports)
_Container =
CompositionContainer(catalog);
//Fill the imports of this object
_Container.ComposeParts(
this
MEFPluginLoader loader =
IEnumerable<IPlugin> plugins = loader.Plugins;
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 resolove 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.
(T);
ICollection<T> plugins =
List<T>(pluginTypes.Count);
T plugin = (T)Activator.CreateInstance(type);