Click here to Skip to main content
15,887,485 members
Articles / Programming Languages / C#

A more extensible way to build plugin system

Rate me:
Please Sign up or sign in to vote.
4.57/5 (7 votes)
29 Aug 2010CPOL3 min read 20.9K   21   14
This post explains how plugin architecture can be improved to allow better extensibility and provide backward compatibility after updates.

A plugin, for those who don’t know, is a component that allows to extend an application without modifying its source code. The application loads plugins at runtime. In .NET, it’s pretty easy to accomplish using Reflection capabilities. The easy way is to expose interfaces in your application assemblies, that the plugin can implement and use, like:

C#
public interface IPluginHost
{
  void DoSomethingOnHost();
}
 
public interface IPlugin
{
  void Initialize(IPluginHost pluginHost);
  int DoSomething();
}

IPlugin interface represents our plugin – all plugins implement this. IPluginHost interface represents the host application. Plugins must often be able to interact with the host application in order to influence how its working.

From within the application, you can load the plugin assemblies and instantiate the plugins using code like the following:

C#
using System.Reflection;
...
IList<IPlugin> pluginList = new List<IPlugin>();
foreach(string fileName in Directory.GetFiles("Plugins", "*.dll"))
{
  Assembly assembly = Assembly.LoadFile(fileName);
  foreach (Type pluginType in assembly.GetTypes())
  {
    if (!pluginType.IsPublic || pluginType.IsAbstract || pluginType.IsInterface)
      continue;
 
    Type concreteType = pluginType.GetInterface(typeof(IPlugin).FullName, true);
 
    if (concreteType != null)
    {
      IPlugin plugin = (IPlugin)Activator.CreateInstance(concreteType);
      plugin.Initialize(pluginHostInstance);
 
      pluginList.Add(plugin);
 
      break;
    }
}
}

The code is simplified, but you can see the point, I hope. We start by loading every .dll file as an Assembly in our application. Then, we iterate through all the public types in that assembly in search for a type that implements IPlugin interface. Once we found it, we load it using Activator.CreateInstance method, assuming that the type has a parameterless constructor. Then we initialize the plugin, giving it an instance of an IPluginHost interface implementation.

Now this approach works fine and I’ve used it several times. However, you usually get into some problems, when you decide to add new functionality. IPlugin interface represents what we need to know about the plugin. IPluginHost interface represents what host application has to offer to the plugin. Those requirements might change quite often, so you will, most likely, make changes to those interfaces. Suppose that we want to allow our plugins to show a message box through its host. That’s easy, we just add a corresponding method to the IPluginHost interface:

C#
public interface IPluginHost
{
  void DoSomethingOnHost();
  void ShowMessageBox(string text);
}

That’s where it all breaks.

Once we release our application and plugins have started to be developed by other people, we should not make changes to the core interfaces, that the plugins depend upon. The thing is, that when you change some interface on the host, the plugin cannot be loaded, because of interface mismatch. So we no longer have backwards-compatibility. How do we extend the plugin subsystem then?

The way I implement plugins now is a bit different. The goal is to keep the core interfaces unchanged. These must be very stable. How do you allow plugins to use new features of your application then?

That’s quite easy. Instead of having a single interface, like IPluginHost – that is almost sure to become a “dependency magnet” or, in other words, a God object, as we add more features to the host application – I utilize the Interface Segregation Principle. It says: “Clients should not be forced to depend on interfaces that they do not use”. So instead of one, big bloated interface we have many small ones. Whenever we decide to add some new functionality to our host application, we add a corresponding interface that plugins can use. In the case of the message box requirement, that I’ve shown above, we declare this:

C#
public interface IMessageBoxHost
{
  void ShowMessageBox(string message);
}

Now, we have to somehow pass the interface to the plugin. Since we can’t modify IPlugin’s declaration (we can’t just overload Initialize method, since that would break backwards compatibility too), we need another way to do this. What I chose to use is constructor injection. We can detect what arguments the constructor of IPlugin implementation have and pass what it needs. For example, if we have a plugin like:

C#
public class Plugin: IPlugin
{
  public Plugin(IMessageBoxHost messageBoxHost)
  {
    // code here
  }
 
  // method implementations here
}

... we pass an instance of IMessageBoxHost implementation when instantiating the plugin:

C#
(IPlugin)Activator.CreateInstance(concreteType, new object[] { messageBoxHostInstance })

That way, we only get what we need. We can also use any of the Dependency Injection frameworks to instantiate the plugin and fill dependencies for us.

Of course, if you spot some bug in the interface or have to change it for some other reason, the problem remains the same – changing interface will break plugins. But it’s also easier to solve – we can add a new, fixed, interface, say IMessageBoxHostEx, and mark the old one as obsolete, thus maintaining backwards compatibility and warning users to let go of the previous one.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Lithuania Lithuania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionEven better way to define a plug-in system Pin
JeremyWilkins29-Sep-14 12:36
JeremyWilkins29-Sep-14 12:36 
GeneralRe: Even better way to define a plug-in system Pin
Gediminas Geigalas30-Sep-14 23:14
Gediminas Geigalas30-Sep-14 23:14 
GeneralRe: Even better way to define a plug-in system Pin
JeremyWilkins8-Oct-14 10:05
JeremyWilkins8-Oct-14 10:05 
In some senses I get what you are saying about different host to plugin or plugin to host support directions, however in the implementation I suggest, it would satisfy both separately and simultaneously, and without getting in the way.

Since the host and client in the architecture I'm suggesting are separate from the plugin support system, none of the hosting code need be in the composition root of your application, and containers can stay where they belong, in the composition root.

You said it yourself, only the host or client needs to know what is demanded by either one, the container can tie them together automatically (through constructor injection).

Either the host or client can receive the interfaces from the other that it needs and do the standard "as [type]" attempts to try to get the extra features it COULD use. It all depends on the architecture you want to develop, but the plugin system does not and should not care about your intended architecture, and the host code can still be responsible for calling the plugin to initialize it through an interface, if you want it to.

Either way, your plugin system gets out of the way and hands control back the application developers to decide how the details should be done, while keeping your code decoupled and not dependent on the plugin system for build-up (it should always be possible to do manually what the container and plugin system does automatically).
Questiondetect the arguments of the constructor? Pin
tttdolph10-Aug-11 12:15
tttdolph10-Aug-11 12:15 
AnswerRe: detect the arguments of the constructor? [modified] Pin
Gediminas Geigalas11-Aug-11 2:51
Gediminas Geigalas11-Aug-11 2:51 
GeneralRe: detect the arguments of the constructor? Pin
tttdolph11-Aug-11 9:38
tttdolph11-Aug-11 9:38 
GeneralMy vote of 5 Pin
Hamish Ahern7-Jul-11 1:16
Hamish Ahern7-Jul-11 1:16 
GeneralGreat Article Pin
Diamonddrake1-Sep-10 5:49
Diamonddrake1-Sep-10 5:49 
GeneralMy vote of 5 Pin
Brian Pendleton31-Aug-10 1:56
Brian Pendleton31-Aug-10 1:56 
GeneralMy vote of 5 Pin
CiskeBusch30-Aug-10 19:57
CiskeBusch30-Aug-10 19:57 
General.net4 have native plugin subsystem Pin
vdasus30-Aug-10 13:43
vdasus30-Aug-10 13:43 
GeneralRe: .net4 have native plugin subsystem Pin
Gediminas Geigalas30-Aug-10 19:26
Gediminas Geigalas30-Aug-10 19:26 
GeneralRe: .net4 have native plugin subsystem Pin
vdasus30-Aug-10 20:44
vdasus30-Aug-10 20:44 
GeneralRe: .net4 have native plugin subsystem Pin
JeremyWilkins29-Sep-14 12:48
JeremyWilkins29-Sep-14 12:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.