1. Introduction
This posting is a continuation from the previous post on Managed Extensibility Framework (MEF). If you are a new to MEF you will need to visit the MEF blog at Codeplex or Glenn’s Blog here. You will need to download the framework so you can also find out how it leverages the power of extensibility into your applications.
In the previous post I showed how you can quickly create a MEF application and use the attributes to import and export different parts, and use the container to compose the parts. My example used parts from the same assembly – using Assembly.GetExecutingAssembly(); but in this example I will include separately compiled assemblies (plugins) which are read by the DirectoryCatalog from a specific location.
So essentially what you will see the following in this post
- The Aggregate Catalog
- The Directory Catalog
- Making the host accessible from a plugin.
- A bit of Metadata attributes
Let look at what we had to do to come up with the new example.
2. The Aggregate Catalog
To do this we had to combine the AssemblyCatalog and the DirectoryCatalog into an AggregateCatalog. The AggregateCatalog combines a number of catalogs into one, and that is exactly what we wanted to do because we wanted the exports from the external assemblies to satisfy the imports of the executing assembly.
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Program program = new Program();
var host = program.Compose();
Application.Run(host as Form1);
}
/// <summary>
/// This will compose the parts as defined
/// </summary>
/// <returns>IPluginHost</returns>
public IPluginHost Compose()
{
var catalog = new AggregateCatalog(); //The special type of catalog that combines a number of catalogs
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new DirectoryCatalog(@".\Plugins", "*.dll"));
//At this poing the catalog contains the (1) ExecutingAssembly and (2) the assenblies in \Plugin folder
CompositionContainer container = new CompositionContainer(catalog);
return container.GetExportedObject<IPluginHost>();
}
3. Contracts
The first thing to do was to create a project which contains contracts. These are simply interfaces that will be shared between the host application and the plugins. The contracts define the expected common operations, data items between plugins and host application. They make a uniform way in which you can interact with those objects, while oblivious about the implementation details. As shown below the PluginHost is the contract implemented by the host application and IPlugin by plugins. You will also notice that we have included the IPluginHost in the IPlugin interface… This is because we want each of the plugins to perform some functions on the plugin host, for example show the progress bar, change theme and so forth.
public interface IPluginHost
{
void ShowProgress(string message, int percentage);
void ChangeTheme(Color color);
}
public interface IPlugin
{
IPluginHost Host {get;}
string Version { get; }
ToolStripItem[] ToolStripItems { get; }
}
4. Plugins
The next thing is to create the plugins. The code below shows that the plugin imports the IPluginHost, which in our case is the main form. We access the host / shell using Host, and as shown in the example below in the button1_Click method. We can send commands to the host ‘s progress bar using Host.ShowProgress(“Processing…”, i).
We have also decided to decorate the plugin with some ExportMetadata attribute which we will access from the container.
[Export(typeof(IPlugin))]
[ExportMetadata("Name", "Process Starter Plugin")]
public partial class ProcessPlugin : UserControl, IPlugin {
[Import(typeof(IPluginHost))]
private IPluginHost host;
public IPluginHost Host {
get { return host; }
}
public ProcessPlugin() {
InitializeComponent();
}
#region IPlugin Members
public string Version {
get { return Assembly.GetExecutingAssembly().GetName().Version.ToString(); }
}
public ToolStripItem[] ToolStripItems {
get { return null; }
}
#endregion
private void button1_Click(object sender, EventArgs e) {
for (int i = 0; i < 100; i++) {
/* Some long process*/ for (int b = 0; b < 10000000; b++)
;
Host.ShowProgress("Processing...", i);
}
Host.ShowProgress("", 0);
}
}
5. The Plugin Host / Shell
This is the main form which hosts the plugins. In it, we have declared an ExportCollection of IPlugin and decorated it with the [Import] attribute, so that during composition we will have all the IPlugin objects created and assigned to it. The advantage of using the ExportCollection than the IEnumerable is that it enables us to also extract the metadata from the exported part.
[Export(typeof(IPluginHost))]
public partial class Form1 : Form,
IPluginHost
{
/// <summary>
/// Plugins will be imported into this property
/// </summary>
[Import(typeof(IPlugin))]
ExportCollection<IPlugin> Plugins { get; set; }
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// Show plugins
listBox1.Items.Clear();
foreach (var plugin in Plugins) {
// get the Plugin
var plg = plugin.GetExportedObject();
// Check if it has toolstrip items and add them to the main form
if (plg.ToolStripItems != null)
toolStrip1.Items.AddRange(plg.ToolStripItems);
// Add the visible area of the plugin to the main form
tableLayoutPanel1.Controls.Add(plg as Control);
// Read the metadata and show the Name Property and the Version value from
// the plugins
listBox1.Items.Add(string.Format(
"{0} {1}", plugin.Metadata["Name"].ToString(), plg.Version));
}
}
#region IHost Members
/// <summary>
/// Show Progress bar with text message. This is controllable from the plugins
/// because it is part of the IPluginHost interface which is known by all
/// plugins.
/// </summary>
/// <param name="message">Message shown on status bar</param>
/// <param name="percentage">progress indicator value</param>
public void ShowProgress(string message, int percentage) {
toolStripStatusLabel1.Text = message;
toolStripProgressBar1.Value = percentage;
Application.DoEvents();
toolStripStatusLabel1.Visible = percentage > 0;
toolStripProgressBar1.Visible = toolStripStatusLabel1.Visible;
}
/// <summary>
/// This changes the color of the background and is controllable from the
/// Plugins.
/// </summary>
/// <param name="color">Color to be use on background</param>
public void ChangeTheme(Color color) {
BackColor = color;
}
#endregion
}
6. Conclusion
This example has attempted to illustrate how you can use the Managed Extensibility Framework to create a small plug-in application. We have also tried to make the host application and plugins independent, and have them abide to the contracts. The contracts were defined and used by the plugins to perform some actions on the plugin-host / shells regardless of the implementation details. The plugins also implemented the contracts which enabled them to have uniformity and to expose common methods and properties.
When the application starts; the running assembly and the plugins are read and mappings made between imports and exports (composition). We then get the exported object called IPluginHost which we know is the main form and run it.
7. Source Code
Download source here
Technorati Tags: C#,.NET,Managed Extensible Framework,Plugin FrameworkPost