Introduction
As applications grow, they tend to be harder and harder to maintain, despite the fact that most applications are more or less well designed from the start. This is usually because it's very easy to increase coupling when you need to add new features to your application. Plugins are a way to solve this by adding new functionality in small testable DLLs.
This example does not contain a lot of text describing everything, instead I'll just show you how to use the plugin system. If you have any thoughts, comment the article or visit the discussion board at Codeplex.
The Plugin System
The plugin system consists of three parts.
The Application
The application creates an instance of the PluginManager which is used to find and load plugins. The plugins are found by specifying a search string which contains directory information and wild cards for the files. You can specify the security settings (Evidence) that each assembly should be loaded with, which is perfect if you have different access levels for your plugins.
Application Interfaces
The second assembly contains the interfaces that the application exposes towards the plugins. This assembly exists since you may not want to send your entire application to third party plugin developers.
Plugins
And finally we got the actual plugin assemblies. Plugins can mark themselves dependent on other plugins. For instance, say that you have a newsletter plugin which generates and sends newsletters. The newsletter plugin needs an emailer which is specified in another plugin. The newsletter plugin can therefore mark itself as dependent on the emailer plugin, as a result of which the emailer will always be loaded before the newsletter plugin.
The Example
In this article, I'll demonstrate how to create a simple plugin.
First of all, we need to specify what the plugins should be able to do, and define which parts of the application are accessible from the plugins. All of this is added into a class library that will be sent to plugin developers.
Applications Shared Assembly
In this example, our application wants to let the plugins be able to register components and add menu items to the main application menu. By letting plugins register components, we got a quite flexible solution since plugins can use components registered by other plugins.
public interface IMyApplication : IApplication
{
void AddMenuItem(string name, MenuItemClickedHandler clickHandler);
void RegisterComponent<T>(T component) where T : class;
T GetComponent<T>() where T : class;
}
public delegate void MenuItemClickedHandler(string name);
And the interface that plugins should implement:
public interface IMyPlugin : IPlugin
{
void SayHelloTo(string name);
}
We don't have high expectations from the plugins, they are just required to say hello to anyone we tell them.
Plugin Interface
The plugin has a component that it will register, thus adding functionality to the application that can be used by the application itself or other plugins.
public interface IWorldPeace
{
void Initiate();
}
The Plugin
The plugin contains two parts: First the class that implements the plugin interface, and then the class that implements IWordPeace
that the plugin introduced.
public class MyPlugin : IMyPlugin
{
private const string _pluginName = "MyPlugin";
private readonly IPluginInfo _pluginInfo = new MyPluginInfo();
private readonly IEnumerable<string> _dependencies = new List<string>();
public string PluginName
{
get { return _pluginName; }
}
public IPluginInfo PluginInfo
{
get { return _pluginInfo; }
}
public IEnumerable<string> Dependencies
{
get { return _dependencies; }
}
public void Start(IApplication application)
{
IMyApplication myApplication = (IMyApplication) application;
myApplication.RegisterComponent<IWorldPeace>(new WorldPeaceThroughLove());
myApplication.AddMenuItem("Click me", OnClick);
}
private void OnClick(string name)
{
Console.WriteLine("Omg! You clicked me!");
}
public void SayHelloTo(string name)
{
Console.WriteLine("Hello " + name);
}
}
And finally the world peace implementation:
public class WorldPeaceThroughLove : IWorldPeace
{
public void Initiate()
{
Console.WriteLine("Love is all around!");
}
}
The Application
We use a console application in this example, but it could be a Winforms or a Web application.
public class Program : IApplication
{
private PluginManager<imyplugin> _pluginManager;
private readonly IDictionary<type,> _components = new Dictionary<type,>();
[STAThread]
public static void Main()
{
new Program().Start();
}
public void AddMenuItem(string name, MenuItemClickedHandler clickHandler)
{
}
public void RegisterComponent>T<(T component) where T : class
{
_components.Add(typeof(T), component);
}
public T GetComponent>T<() where T : class
{
return (T)_components[typeof(T)];
}
private void Start()
{
_pluginManager = new PluginManager<IMyPlugin>(this);
_pluginManager.PluginPath = "*Plugin.dll";
_pluginManager.Start();
_pluginManager["MyPlugin"].SayHelloTo( "everyone!");
IWorldPeace worldPeace = GetComponent<IWorldPeace><iworldpeace>();
worldPeace.Initiate();
}
}
Update: Unloading Plugins
I've got some questions about how to unload plugins. I have no support for this in the plugin system and I'll try to explain why.
First of all, there is no way to unload assemblies (DLLs) from an appdomain (application). Period.
Solution: Appdomains and Remoting
Although there's a way around it: using a secondary appdomain for all plugins, this means that the plugins are in something that can be considered as an external application (each appdomain has isolated memory and an unhandled exception in the secondary appdomain will not affect the primary appdomain) which means that you need to use remoting to be able to communicate between the plugins and the main application.
Having all plugins in the secondary appdomain means that you can't unload a single plugin, you have to unload all of them. But hey, you can use one appdomain for each plugin, right? Well sure; if you can live with the performance hit.
Another Problem
Let's say that you have a plugin which provides an addressbook with contacts to the rest of the application. Do you really want to unload it? Let's say that the application (or any of the other plugins) contain references to some of your contacts, what happens to them if you unload that plugin?
Unloading stuff is much more complex than you might think.
Final Words
That's it. Let me know if there's something that you did not understand.
Feel free to check my other projects: C# Webserver and TinyDAL.
History
- 30th August, 2008: Initial post