Click here to Skip to main content
Click here to Skip to main content

PluginManager - A C# utility to load plug-in based components

, 3 Dec 2008 CC (ASA 2.5)
Rate this:
Please Sign up or sign in to vote.
PluginManager is a simple desktop utility that loads and runs simple control based plug-ins.

Introduction

This article presents and discusses a desktop application called PluginManager, which is a GUI "shell" to load and run pluggable controls. It uses C#, .NET 3.5, and WinForms. This utility is developer centric. It is not a complete application by itself, but provides a simple means to load up custom controls and applications. To demonstrate the tool and the plug-in creation, a simple "StickyNote" plug-in is discussed.

Background

Plug-ins and plug-in based software

The more general definition of what a plug-in is and what constitutes pluggable software can be found in numerous external sources (see the References section). A definition of plug-ins (verbatim from Wikipedia) is given below:

In computing, a plug-in (also: plugin, addin, add-in, addon, add-on, snap-in, or snapin; but see also extension) consists of a computer program that interacts with a host application (a web browser or an email client, for example) to provide a certain, usually very specific, function "on demand".

Our (over) simplified definition of a plug-in

Our approach to the concept of a "plug-in" deviates from the more standard definition. In the current context, a plug-in is any .NET WinForms UI Control type that has a PluginAttribute marked against its type. This tool will load any such type identified in specified assemblies and treat them as "plugins".

There are no contract interfaces to implement, there are no restrictions on what the plug-in component can do. Thus, the term plugin is loosely applied in this article.

The various concepts used by the PluginManager include:

  • Runtime inspection of assemblies using Reflection
  • Loading plug-in controls during runtime using object activation
  • Use of controls that extend the behaviour of the standard ListBox, TreeView, TabControl, and TabPage.
  • XML serialization and deserialization
  • Sample plug-ins are provided to illustrate how to create new plug-ins

Prerequisites

Readers are expected to have a basic understanding of:

  • Writing WinForms applications in C#.NET
  • Reflection and Attributes
  • XML serialization

Overview

The main idea behind this utility is simple - create a simple GUI application that can discover and load controls during application runtime. The custom controls, in effect, are treated as mini-applications.

For example, a custom plug-in control can provide functionality similar to a sticky notes application.

PluginManagerOverview.GIF

The figure above illustrates the key components in PluginManager, which are discussed in detail below.

PluginManager UI shell

This is the main host application. On first-time startup, the application looks as below.

PluginManagerStartup.GIF

The main UI is divided into two main sections. The left hand panel contains the list of discovered plug-ins, and the right hand panel contains the area to load the plug-ins. The list of available plug-ins are loaded from an XML configuration file called PluginStore.xml. The location of this file is under the plugins folder in the main application root folder.

When PluginManager starts up, it checks for the plug-in store file and lists all the available plug-ins from there.

Plugins folder

As mentioned earlier, the plugins directory is a special directory that hosts the plug-in store XML file and folders for each plug-in. PluginManager searches for plug-ins placed in single level subdirectories under the plugins folder. In the sample application attached, you should find a folder called StickyNote under the plugins folder. Each subfolder can contain as many assemblies as possible that contain plug-ins. For ease of use, a simple norm is to have one folder for related plug-ins. The CoreSet folder contains plug-ins like About, HelloWorld, and PluginStoreViewer.

Plugin icons

If you want your plug-in to have a customised icon displayed when it is loaded, you need to place an .ico file with the following naming convention: <PluginControlTypeName>.ico.

The StickyNote folder contains an icon file called StickyNote.ico as StickyNote is the name of the plugin control class.

PluginStore Refresh dialog

The Refresh button triggers the process of refreshing the plug-in store. The plug-in refersh process can be outlined as:

  • For every folder in the plugins folder
    • For every file that ends with .exe or .dll
      • Try to load the assembly. Use Reflection to enumerate types in the assembly.
      • Find out Control inherited types that are marked with PluginAttribute.
      • Check for the existence of icons for discovered types.
      • Save the details of these types into the plug-in store.

Once all possible plug-ins are found and the plug-in store gets refreshed, the application reloads the plug-in list.

PluginManagerRefreshed.GIF

The figure above shows the application after the a refresh. Plug-ins can be loaded by clicking on their names in the plug-ins list. The figure below shows the PluginStoreViewer plug-in in use.

PluginManagerStoreViewer.GIF

The plug-in store view plug-in is a simple tree view list of the PluginStore.xml file. It displays all the information that is saved about various plug-ins.

Sample plug-in: StickyNote

I have included a sample plug-in called StickyNote. This is a very simple note taking application that you can use to jot down quick notes; these notes are saved automatically by the plug-in, and can be loaded anytime later on. It is more of a demonstration on writing plug-ins for PluginManager than anything else.

PluginManagerStickyNote.GIF

The figure above shows the StickyNote plug-in in use. All plug-in instances are loaded in its own tab on the right hand side.

Using the code

The main solution file attached contains the following projects:

Citrus.Forms

This is a simple class library that contains the definition for PluginAttribute. This attribute is used to markup plug-in controls for use with the plug-in manager. For example:

[Plugin("The Hello World Plugin", "A sample hello world plugin")]
public partial class HelloWorld : UserControl
{
    // Plugin logic here...
}

You must use Citrus.Forms.dll in your custom plug-in class library to reference PluginAttribute.

Citrus.Forms.PluginManager

This is the main WinForms application project. You will find the code to all the main application classes in this project.

Note: You will find the classes organised in folders named Model, View, and Controller. These are just for organisation. The application, at the moment, does not use the MVC pattern. I may update the application to use this later on.

The main classes are:

  • Model/PluginStore
  • Model/PluginInfo
  • These classes define the object representation of the plug-in store XML. They help in serializing and deserializing the plug-in store file.

    [XmlRoot("PluginStore")]
    public class PluginStore
    {
        public List<PluginInfo> Plugins { get; set; }
    
        public PluginStore() { Plugins = new List<PluginInfo>(); } 
    }
    
    public class PluginInfo
    {
        [XmlAttribute("Name")]
        public string Name { get; set; }
    
        [XmlAttribute("Description")]
        public string Description { get; set; }
    
        [XmlAttribute("Type")]
        public string Type { get; set; }
    
        [XmlAttribute("AssemblyFile")]
        public string AssemblyFile { get; set; }
    
        [XmlAttribute("InstallPath")]
        public string InstallPath { get; set; }
    
        [XmlAttribute("Icon")]
        public string Icon { get; set; }
    
        public override string ToString()
        {
            return Name;
        }
    }

    A sample plug-in store XML file is listed below:

    <?xml version="1.0" encoding="utf-8"?>
    <PluginStore
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    >
      <Plugins>
        <PluginInfo 
        Name="About" 
        Description="About Plugin Manager" 
        Type="Citrus.Forms.PluginManager.Plugins.About" 
        AssemblyFile="plugins\CoreSet\Citrus.Forms.PluginManager.Plugins.dll"
        InstallPath="plugins\CoreSet" Icon="\Resources\PluginManager.ico" 
         />
      </Plugins>
    </PluginStore>
  • Controller/EnvironmentSettings
  • A simple utility class to provide environment related functions.

  • Controller/IconSet
  • PluginManager assigns icons to plug-ins it discovers. This class provides an in-memory cache of all icons used by the application. It uses a ListDictionary object to map icon file names and icons.

    Choosing the right collection data structure in your application can make all the difference for performance.

  • Controller/PluginStoreManager
  • Key concepts: XML serialization and deserialization, Reflection, Attribute inspection.

    This class does the nitty gritty of refreshing and loading the plug-in store. Loading plug-ins is a simple process of deserializing the PluginStore.xml file.

    /// <summary>
    /// Loads the plugins set up in the plugin store
    /// </summary>
    /// <returns></returns>
    public static PluginStore LoadPluginStore()
    {
        if (!File.Exists(PluginStoreFile))
            return null;
    
        StreamReader reader = new StreamReader(PluginStoreFile);
        PluginStore pluginStore = (PluginStore)serializer.Deserialize(reader);
        reader.Close();
    
        return pluginStore;
    }

    PluginStoreManager exposes three events that are raised when a refresh operation is in progress. Check the RefreshPuginStore method for details.

    The events are OnPluginStoreRefreshStarting, OnPluginStoreRefreshCompleted, and OnPluginStoreRefreshProgress. The first two indicate the start and completion of the refresh process. The third event is triggered whenever a new plug-in component is found during a refresh operation. All three events are hooked on to in the PluginStoreRefreshDialog class.

  • View/PluginListBox
  • View/PluginTabControl
  • View/PluginTabPage
  • Key concepts: Extending standard UI controls.

    These are simple extensions to the default ListBox, TabControl, and the TabPage controls of .NET. ListBox has been extended to display plug-in names and associated icons in a list. The TabControl and TabPage have simple properties that are used to reference the loaded plug-in.

  • View/PluginStoreRefreshDialog
  • Key concepts: Hooking events using delegates, asynchronous method execution.

    This class implements a simple form that can trigger the plug-in store refresh task and display the progress of the operation in a Progress control.

  • View/PluginManager
  • Key concepts: Object activation, asynchronous method execution.

    This is the main application form. Its hosts the various extended controls we just discussed, and loads up plug-ins on demand. The main UI, as stated before, is very simple. The main method that loads the selected plug-in is listed below for reference.

    public delegate void PluginAction(object plugin);
    
    /// <summary>
    /// Loads the specified plugin
    /// </summary>
    /// <param name="plugin"></param>
    public void DoPluginLoad(object plugin)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(new PluginAction(DoPluginLoad), new object[] { plugin });
        }
        else
        {
            Cursor = Cursors.AppStarting;
            PluginInfo p = (PluginInfo)plugin;
            try
            {
    
                Control pluginControl = (Control)Activator.CreateInstance(
                    Assembly.LoadFile(EnvironmentSettings.GetFullPath(
                    p.AssemblyFile)).GetType(p.Type)
                );
    
                
                PluginTabs.LoadPlugin(p, pluginControl);
                UpdateCurrentPluginInUse(p);
            }
            catch (Exception e)
            {
                DisplayPluginLoadError(p, e);
            }
    
            Cursor = Cursors.Default;
        }
    }
Citrus.Forms.PluginManager.Plugins

This project contains the CoreSet plug-ins. These include:

  • About plug-in - displays about information in a plug-in.
  • Plugin Store Viewer plug-in - implements a plug-in that shows a custom tree view of the plug-in store contents
  • Hello World plug-in - Lest ye forget.
Citrus.Forms.StickyNote

This is a sticky note plug-in application that lets you write notes that are sticky (it means you don't have to save these small notes). There are better sticky notes applications out there, and this is more of an example of what can be done with PluginManager than anything else.

Points of interest

PluginManager does not enforce any restrictions on what the plug-ins can do. Plug-ins are loaded at runtime, and are allowed to perform any logical operation - which might even include deleting folders used by other plug-ins. This non-restrictive model is deliberate. This tool is developer centric, and just explores the possibilities of loading runtime controls and adding them to the main application form.

It is a very simple trust based system - plug-ins discovered will be trusted to behave properly, blindly. Thus, one may argue that it is not a true plug-in, as the runtime components do not need to adhere to a well defined service interface defined by the host application. This argument is left to the reader.

Developing real plug-in-based applications is a more involving task. There are a lot of good application frameworks (.NET and others) out there that simplify plug-in based software development. Check the References section below.

Acknowledgements

This project uses the following external resources:

Further references

Refer to the following for details on full-fledged plug-in development:

The idea list

A couple of random ideas to extend PluginManager from its current infant stage:

  • More plug-ins.
  • Plug-ins can have categories.
  • Searching for plug-ins from some repository (based on category?).
  • (Better) exception handling. At the moment, this is almost non-existent!

Final remarks

PluginManager is only as good as the plug-ins that are available for it. If you feel like you have a neat idea or have developed a cool plug-in and want to share it, let me know so that I can reference it in this article. If you have ideas, suggestions, or comments on anything about this utility, let me know. I'll try to put in some useful plug-ins later on. Cheers.

History

  • 03 Dec 2008. Initial public release.

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-ShareAlike 2.5 License

Share

About the Author

Benzi K. Ahamed
Web Developer
United Kingdom United Kingdom
I work as a Technology Lead for an IT services company based in India.
 
Passions include programming methodologies, compiler theory, cartooning and calligraphy.

Comments and Discussions

 
GeneralModules Communications PinmemberElManoSanta12-Aug-09 5:19 
QuestionHuh? PinmemberPIEBALDconsult4-Dec-08 10:25 
AnswerRe: Huh? PinmemberBenzi K. Ahamed5-Dec-08 0:14 
I was under the impression that implementing an interface and extending a class were quite different concepts. Please correct me if I am wrong or I did not get your question right.
 
Again, as mentioned, you need to extend the Control class + mark it up with PluginAttribute for it to be treated as a plugin.
 
Of course you can create your own FooBar class for which it will fail, as PluginManager does not know about FooBar classes in general.
 
Cheers,
Benzi K. Ahamed

GeneralRe: Huh? PinmemberPIEBALDconsult5-Dec-08 5:25 
GeneralRe: Huh? PinmemberBenzi K. Ahamed8-Dec-08 1:21 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 3 Dec 2008
Article Copyright 2008 by Benzi K. Ahamed
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid