Simple plugin host application approach in C#
Building simple plugin host application in C#
A lot of, especially desktop applications implement plugin approach in order to make application more expendable and allow other vendors to include their specific functionality related to applications with plugins.
Usually plugins are available from some sort of repository from which user can select and download them directly to application. I will not go deeper in approach for this in this article. Instead I will introduce simple approach how to load plugins and initiate some portion of functionality of loaded plugin. Distributing and managing plugins might be described in some future article.
In case text is boring for you, you can jump directly to code and solution by downloading complete source from a link on the right side.
Now to begin with the core of the whole plugin story.
Usually plugins consist of a dll where custom functionality is coded and maybe some config or other additional files. In most cases each installed application plugin has it's own folder where it keeps all plugin related files.
Since this is just an example, I will keep all the plugin dlls in the same folder under Plugins folder in application. First we'll build a base library for every future plugin we are going to build.
We'll call this class library project ApplicationPlugins. After project is created we need to add interface which will force plugin developer to unify the structure of each plugin.
To keep things simple we'll just create one property and one method in this interface which we'll use later on.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ApplicationPlugins { public interface IPlugin { ConsoleColor TextColor { get; } void DoSomeWork(); } }
Next thing will be abstract class which will implement this interface. The reason we are creating abstract class is that we can put in this class a piece of functionality which is common for all plugins.
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace ApplicationPlugins{public abstract class Plugin : IPlugin{public virtual ConsoleColor TextColor{get { throw new NotImplementedException(); }}public abstract void DoSomeWork(); } }
Now when we have molf for plugins we can start creating them. Each plugin we are going to create in this example will be in it's own class library and will be build as separate dll. You can however create all pluging in one file, but for distribution you certainly want to allow user to separately download plugins and that's why we are going to keep them as separate class libraries.
For first plugin we'll create class library named Plugin1 and create plugin class in this library.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Plugin1 { public class Plugin : ApplicationPlugins.Plugin { public override ConsoleColor TextColor { get { return ConsoleColor.Red; } } public Plugin() { Console.ForegroundColor = this.TextColor; Console.WriteLine("Hello from plugin 1"); } public override void DoSomeWork() { Console.WriteLine("Plugin 1 numbers:"); for (int num = 1; num <= 5; num++) { Console.WriteLine(num.ToString()); Thread.Sleep(1000); } } } }
The same way we'll create second plugin with some different value for the property and little bit different method implementation.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Plugin2 { public class Plugin : ApplicationPlugins.Plugin { public override ConsoleColor TextColor { get { return ConsoleColor.Green; } } public Plugin() { Console.ForegroundColor = this.TextColor; Console.WriteLine("Hello from plugin 2"); } public override void DoSomeWork() { Console.WriteLine("Plugin 2 characters:"); for (char chr = 'A'; chr <= 'E'; chr++) { Console.WriteLine(chr.ToString()); Thread.Sleep(1000); } } } }
Now when we have plugins, we just need to host them in our application. For that we'll create another project in a solution which will be console application and name it PluginHost.
As I mentioned before we'll keep plugin libraries in Plugins folder. Since the folder of PluginHost executing file will be PluginHost\bin\Debug will create new folder PluginHost\bin\Debug\Plugins.
Before we start writing host code will configure Plugin1 and Plugin2 class library project to copy output files to Plugins folder of PluginHost so we do not have to do it manually after every build.
In properties of each project, in build events we'll add post build event with the following command:
copy /Y "$(TargetPath)" "$(SolutionDir)\PluginHost\bin\Debug\Plugins"
This command will copy library dll to plugins folder of host application.
Now we'll add code in console application which will load plugins from plugin folder, initiate instances of classes that implement IPlugin and invoke method DoSomeWork on both plugins.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Reflection; using System.IO; namespace PluginHost { class Program { static void Main(string[] args) { string pluginsFolder = Path.Combine( Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Plugins"); foreach (string pluginPath in Directory.GetFiles(pluginsFolder, "Plugin*.dll", SearchOption.TopDirectoryOnly)) { Assembly newAssembly = Assembly.LoadFile(pluginPath); Type[] types = newAssembly.GetExportedTypes(); foreach (var type in types) { //If Type is a class and implements IPlugin interface if (type.IsClass && (type.GetInterface(typeof(ApplicationPlugins.IPlugin).FullName) != null)) { var ctor = type.GetConstructor(new Type[] { }); var plugin = ctor.Invoke(new object[] { }) as ApplicationPlugins.IPlugin; plugin.DoSomeWork(); } } } Console.WriteLine("Press any key to end"); Console.Read(); } } }
This is just a simple example with a console host application, but you plugin can represent UI as well if then inherit base UserControl class if you are doing WinForms or UserControl, WebUserControls for WebForms of maybe even HtmlHelper for MVC applucations.
The core thing is to follow pattern for creating plugin which means all plugins will inherit the same base which will make them all fit into the same mold.
The second important thing is host application code. It is not that complicated and you can pretty much use the one from this example with some small changes to fit your requirements. Assembly loading can be the same.
References
Disclaimer
Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.
Comments for this article